diff --git a/binaries/data/mods/public/shaders/arb/overlayline.fp b/binaries/data/mods/public/shaders/arb/overlayline.fp
index fd78472..bde918c 100644
--- a/binaries/data/mods/public/shaders/arb/overlayline.fp
+++ b/binaries/data/mods/public/shaders/arb/overlayline.fp
@@ -4,13 +4,16 @@ TEMP base;
 TEMP mask;
 TEMP color;
 
-
 // Combine base texture and color, using mask texture
 TEX base, fragment.texcoord[0], texture[0], 2D;
 TEX mask, fragment.texcoord[0], texture[1], 2D;
-LRP color.rgb, mask, objectColor, base;
+#if USE_OBJECTCOLOR
+  LRP color.rgb, mask, objectColor, base;
+#else
+  LRP color.rgb, mask, fragment.color, base;
+#endif
 
-#ifdef IGNORE_LOS
+#if IGNORE_LOS
   MOV result.color.rgb, color;
 #else
   // Multiply RGB by LOS texture (alpha channel)
@@ -19,9 +22,11 @@ LRP color.rgb, mask, objectColor, base;
   MUL result.color.rgb, color, los.a;
 #endif
 
-// Use alpha from base texture, combined with the object color alpha.
-// The latter is usually 1, so this basically comes down to base.a
-MUL result.color.a, objectColor.a, base.a;
-
+// Use alpha from base texture, combined with the object color/fragment alpha.
+#if USE_OBJECTCOLOR
+  MUL result.color.a, objectColor.a, base.a;
+#else
+  MUL result.color.a, fragment.color.a, base.a;
+#endif
 
 END
diff --git a/binaries/data/mods/public/shaders/arb/overlayline.xml b/binaries/data/mods/public/shaders/arb/overlayline.xml
index 07383bc..6dd0031 100644
--- a/binaries/data/mods/public/shaders/arb/overlayline.xml
+++ b/binaries/data/mods/public/shaders/arb/overlayline.xml
@@ -1,9 +1,16 @@
 <?xml version="1.0" encoding="utf-8"?>
 <program type="arb">
 
+	<!-- This shader is used for rendering overlay lines (e.g. territory boundaries), and also for 
+	rendering the unit selection ring quad overlays, since they are almost identical. The only
+	difference is that in unit selection ring mode, the uniform objectColor is ignored and instead
+	replaced by a color stream from the vertices. The USE_OBJECTCOLOR define is used to switch
+	between either mode. -->
+	
     <vertex file="arb/overlayline.vp">
         <stream name="pos"/>
         <stream name="uv0"/>
+        <stream name="color" if="!USE_OBJECTCOLOR"/>
         <uniform name="losTransform" loc="0" type="vec2"/>
     </vertex>
 
@@ -11,7 +18,7 @@
         <uniform name="baseTex" loc="0" type="sampler2D"/>
         <uniform name="maskTex" loc="1" type="sampler2D"/>
         <uniform name="losTex" loc="2" type="sampler2D"/>
-        <uniform name="objectColor" loc="0" type="vec3"/>
+        <uniform name="objectColor" loc="0" type="vec3" if="USE_OBJECTCOLOR"/>
     </fragment>
 
 </program>
diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
index 4be58ad..4d458dd 100644
--- a/binaries/data/mods/public/simulation/components/GuiInterface.js
+++ b/binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -495,7 +495,6 @@ GuiInterface.prototype.IsStanceSelected = function(player, data)
 GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
 {
 	var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
-
 	var playerColours = {}; // cache of owner -> colour map
 	
 	for each (var ent in cmd.entities)
@@ -504,14 +503,7 @@ GuiInterface.prototype.SetSelectionHighlight = function(player, cmd)
 		if (!cmpSelectable)
 			continue;
 
-		if (cmd.alpha == 0)
-		{
-			cmpSelectable.SetSelectionHighlight({"r":0, "g":0, "b":0, "a":0});
-			continue;
-		}
-
 		// Find the entity's owner's colour:
-
 		var owner = -1;
 		var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
 		if (cmpOwnership)
diff --git a/binaries/data/mods/public/simulation/templates/special/territory_block.xml b/binaries/data/mods/public/simulation/templates/special/territory_block.xml
index 7774fb6..0ee7160 100644
--- a/binaries/data/mods/public/simulation/templates/special/territory_block.xml
+++ b/binaries/data/mods/public/simulation/templates/special/territory_block.xml
@@ -23,6 +23,12 @@
   </Position>
   <Selectable>
     <EditorOnly/>
+    <Overlay>
+      <Texture>
+        <MainTexture>circle/128x128.png</MainTexture>
+        <MainTextureMask>circle/128x128_mask.png</MainTextureMask>
+      </Texture>
+    </Overlay>
   </Selectable>
   <TerritoryInfluence>
     <OverrideCost>64</OverrideCost>
diff --git a/binaries/data/mods/public/simulation/templates/special/territory_pull.xml b/binaries/data/mods/public/simulation/templates/special/territory_pull.xml
index bd8c4ac..2c2be00 100644
--- a/binaries/data/mods/public/simulation/templates/special/territory_pull.xml
+++ b/binaries/data/mods/public/simulation/templates/special/territory_pull.xml
@@ -23,6 +23,12 @@
   </Position>
   <Selectable>
     <EditorOnly disable=""/>
+    <Overlay>
+      <Texture>
+        <MainTexture>circle/128x128.png</MainTexture>
+        <MainTextureMask>circle/128x128_mask.png</MainTextureMask>
+      </Texture>
+    </Overlay>
   </Selectable>
   <TerritoryInfluence>
     <OverrideCost>0</OverrideCost>
diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_flora_bush_berry.xml b/binaries/data/mods/public/simulation/templates/template_gaia_flora_bush_berry.xml
index e979c8e..65fdcaf 100644
--- a/binaries/data/mods/public/simulation/templates/template_gaia_flora_bush_berry.xml
+++ b/binaries/data/mods/public/simulation/templates/template_gaia_flora_bush_berry.xml
@@ -18,6 +18,12 @@
   </ResourceSupply>
   <Selectable>
     <EditorOnly disable=""/>
+    <Overlay>
+      <Texture>
+        <MainTexture>circle/128x128.png</MainTexture>
+        <MainTextureMask>circle/128x128_mask.png</MainTextureMask>
+      </Texture>
+    </Overlay>
   </Selectable>
   <Sound>
     <SoundGroups>
diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml b/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml
index ca3e80f..096c4e7 100644
--- a/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml
+++ b/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml
@@ -17,6 +17,12 @@
   </ResourceSupply>
   <Selectable>
     <EditorOnly disable=""/>
+    <Overlay>
+      <Texture>
+        <MainTexture>circle/128x128.png</MainTexture>
+        <MainTextureMask>circle/128x128_mask.png</MainTextureMask>
+      </Texture>
+    </Overlay>
   </Selectable>
   <Sound>
     <SoundGroups>
diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_geo_mineral.xml b/binaries/data/mods/public/simulation/templates/template_gaia_geo_mineral.xml
index f3a147a..0f17d2c 100644
--- a/binaries/data/mods/public/simulation/templates/template_gaia_geo_mineral.xml
+++ b/binaries/data/mods/public/simulation/templates/template_gaia_geo_mineral.xml
@@ -17,6 +17,13 @@
   </ResourceSupply>
   <Selectable>
     <EditorOnly disable=""/>
+    <Overlay>
+      <Outline>
+        <LineTexture>outline_border.png</LineTexture>
+        <LineTextureMask>outline_border_mask.png</LineTextureMask>
+        <LineThickness>0.2</LineThickness>
+      </Outline>
+    </Overlay>
   </Selectable>
   <Sound>
     <SoundGroups>
diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_geo_rock.xml b/binaries/data/mods/public/simulation/templates/template_gaia_geo_rock.xml
index a954293..a247c15 100644
--- a/binaries/data/mods/public/simulation/templates/template_gaia_geo_rock.xml
+++ b/binaries/data/mods/public/simulation/templates/template_gaia_geo_rock.xml
@@ -17,6 +17,12 @@
   </ResourceSupply>
   <Selectable>
     <EditorOnly disable=""/>
+    <Overlay>
+      <Texture>
+        <MainTexture>circle/128x128.png</MainTexture>
+        <MainTextureMask>circle/128x128_mask.png</MainTextureMask>
+      </Texture>
+    </Overlay>
   </Selectable>
   <Sound>
     <SoundGroups>
diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_ruins.xml b/binaries/data/mods/public/simulation/templates/template_gaia_ruins.xml
index f9ee70a..408ccb6 100644
--- a/binaries/data/mods/public/simulation/templates/template_gaia_ruins.xml
+++ b/binaries/data/mods/public/simulation/templates/template_gaia_ruins.xml
@@ -24,6 +24,12 @@
   </ResourceSupply>
   <Selectable>
     <EditorOnly disable=""/>
+    <Overlay>
+      <Texture>
+        <MainTexture>circle/128x128.png</MainTexture>
+        <MainTextureMask>circle/128x128_mask.png</MainTextureMask>
+      </Texture>
+    </Overlay>
   </Selectable>
   <Sound>
     <SoundGroups>
diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_treasure.xml b/binaries/data/mods/public/simulation/templates/template_gaia_treasure.xml
index 15aea8a..4f11e37 100644
--- a/binaries/data/mods/public/simulation/templates/template_gaia_treasure.xml
+++ b/binaries/data/mods/public/simulation/templates/template_gaia_treasure.xml
@@ -29,5 +29,11 @@
   </Sound>
   <Selectable>
     <EditorOnly disable=""/>
+    <Overlay>
+      <Texture>
+        <MainTexture>circle/128x128.png</MainTexture>
+        <MainTextureMask>circle/128x128_mask.png</MainTextureMask>
+      </Texture>
+    </Overlay>
   </Selectable>
 </Entity>
diff --git a/binaries/data/mods/public/simulation/templates/template_structure.xml b/binaries/data/mods/public/simulation/templates/template_structure.xml
index d27bdd5..c7fdbd5 100644
--- a/binaries/data/mods/public/simulation/templates/template_structure.xml
+++ b/binaries/data/mods/public/simulation/templates/template_structure.xml
@@ -66,6 +66,15 @@
     <LineCostClass>default</LineCostClass>
     <LinePassabilityClass>default</LinePassabilityClass>
   </RallyPointRenderer>
+  <Selectable>
+    <Overlay>
+      <Outline>
+        <LineTexture>outline_border.png</LineTexture>
+        <LineTextureMask>outline_border_mask.png</LineTextureMask>
+        <LineThickness>0.4</LineThickness>
+      </Outline>
+    </Overlay>
+  </Selectable>
   <Sound>
     <SoundGroups>
       <select>interface/select/building/sel_universal.xml</select>
diff --git a/binaries/data/mods/public/simulation/templates/template_unit.xml b/binaries/data/mods/public/simulation/templates/template_unit.xml
index d539d87..8afd0fc 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit.xml
@@ -80,6 +80,14 @@
       <metal>20</metal>
     </Capacities>
   </ResourceGatherer>
+  <Selectable>
+    <Overlay>
+      <Texture>
+        <MainTexture>circle/128x128.png</MainTexture>
+        <MainTextureMask>circle/128x128_mask.png</MainTextureMask>
+      </Texture>
+    </Overlay>
+  </Selectable>
   <StatusBars>
     <BarWidth>2.0</BarWidth>
     <BarHeight>0.333</BarHeight>
diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
index 736abae..afadec1 100644
--- a/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
+++ b/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
@@ -70,6 +70,14 @@
       <metal.ore>1</metal.ore>
     </Rates>
   </ResourceGatherer>
+  <Selectable>
+    <Overlay>
+      <Texture>
+        <MainTexture>circle/128x128.png</MainTexture>
+        <MainTextureMask>circle/128x128_mask.png</MainTextureMask>
+      </Texture>
+    </Overlay>
+  </Selectable>
   <Sound>
     <SoundGroups>
       <select>voice/hellenes/civ/civ_male_select.xml</select>
diff --git a/binaries/system/ActorEditor.exe b/binaries/system/ActorEditor.exe
deleted file mode 100644
index 15cdfc5..0000000
Binary files a/binaries/system/ActorEditor.exe and /dev/null differ
diff --git a/source/graphics/ModelAbstract.h b/source/graphics/ModelAbstract.h
index 1686847..60baec3 100644
--- a/source/graphics/ModelAbstract.h
+++ b/source/graphics/ModelAbstract.h
@@ -89,7 +89,7 @@ public:
 	virtual void SetDirtyRec(int dirtyflags) = 0;
 
 	/// Returns world space bounds of this object and all child objects.
-	virtual const CBoundingBoxAligned GetWorldBoundsRec() { return GetWorldBounds(); }
+	virtual const CBoundingBoxAligned GetWorldBoundsRec() { return GetWorldBounds(); } // default implementation
 
 	/**
 	 * Returns the world-space selection box of this model. Used primarily for hittesting against against a selection ray. The 
diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h
index ce6f7c4..556c5bd 100644
--- a/source/graphics/Overlay.h
+++ b/source/graphics/Overlay.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -20,8 +20,10 @@
 
 #include "graphics/RenderableObject.h"
 #include "graphics/Texture.h"
+#include "maths/Vector2D.h"
 #include "maths/Vector3D.h"
 #include "ps/Overlay.h" // CColor  (TODO: that file has nothing to do with overlays, it should be renamed)
+#include "simulation2/components/ICmpFootprint.h"
 
 class CTerrain;
 
@@ -35,11 +37,9 @@ struct SOverlayLine
 
 	CColor m_Color;
 	std::vector<float> m_Coords; // (x, y, z) vertex coordinate triples; shape is not automatically closed
-	u8 m_Thickness; // pixels
+	u8 m_Thickness; // in pixels
 
-	/// Utility function; pushes three vertex coordinates at once onto the coordinates array
 	void PushCoords(const float x, const float y, const float z) { m_Coords.push_back(x); m_Coords.push_back(y); m_Coords.push_back(z); }
-	/// Utility function; pushes a vertex location onto the coordinates array
 	void PushCoords(const CVector3D& v) { PushCoords(v.X, v.Y, v.Z); }
 };
 
@@ -64,10 +64,10 @@ struct SOverlayTexturedLine
 	};
 
 	SOverlayTexturedLine()
-		: m_Terrain(NULL), m_Thickness(1.0f), m_Closed(false), m_AlwaysVisible(false), m_StartCapType(LINECAP_FLAT), m_EndCapType(LINECAP_FLAT)
-	{}
+		: m_SimContext(NULL), m_Thickness(1.0f), m_Closed(false), m_AlwaysVisible(false),
+		  m_StartCapType(LINECAP_FLAT), m_EndCapType(LINECAP_FLAT)
+	{ }
 
-	CTerrain* m_Terrain;
 	CTexturePtr m_TextureBase;
 	CTexturePtr m_TextureMask;
 	CColor m_Color; ///< Color to apply to the line texture
@@ -79,6 +79,7 @@ struct SOverlayTexturedLine
 	LineCapType m_StartCapType; ///< LineCapType to be used at the start of the line
 	LineCapType m_EndCapType; ///< LineCapType to be used at the end of the line
 
+	const CSimContext* m_SimContext; /// Simulation context applicable for this overlay line; used to obtain terrain information
 	shared_ptr<CRenderData> m_RenderData; ///< Cached renderer data (shared_ptr so that copies/deletes are automatic)
 
 	/**
@@ -86,6 +87,14 @@ struct SOverlayTexturedLine
 	 * If the input string is unrecognized, a warning is issued and a default value is returned.
 	 */
 	static LineCapType StrToLineCapType(const std::wstring& str);
+
+	void PushCoords(const float x, const float z) { m_Coords.push_back(x); m_Coords.push_back(z); }
+	void PushCoords(const CVector2D& v) { PushCoords(v.X, v.Y); }
+	void PushCoords(const std::vector<CVector2D>& points)
+	{
+		for (size_t i = 0; i < points.size(); ++i)
+			PushCoords(points[i]);
+	}
 };
 
 /**
@@ -99,6 +108,19 @@ struct SOverlaySprite
 	float m_X0, m_Y0, m_X1, m_Y1; // billboard corner coordinates, relative to base position
 };
 
+/**
+ * Rectangular single-quad terrain overlay, with world space coordinates. The vertices of the quad
+ * are not required to be coplanar; the quad is arbitrarily triangulated with no effort being made to
+ * find a best fit to the underlying terrain.
+ */
+struct SOverlayQuad
+{
+	CTexturePtr m_Texture;
+	CTexturePtr m_TextureMask;
+	CVector3D m_Corners[4];
+	CColor m_Color;
+};
+
 // TODO: OverlayText
 
 #endif // INCLUDED_GRAPHICS_OVERLAY
diff --git a/source/graphics/RenderableObject.h b/source/graphics/RenderableObject.h
index c3784fd..cdc702a 100644
--- a/source/graphics/RenderableObject.h
+++ b/source/graphics/RenderableObject.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2009 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -57,14 +57,16 @@ class CRenderableObject
 
 public:
 	// constructor
-	CRenderableObject() : m_RenderData(0), m_BoundsValid(false) {
+	CRenderableObject() : m_RenderData(0), m_BoundsValid(false)
+	{
 		m_Transform.SetIdentity();
 	}
 	// destructor
 	virtual ~CRenderableObject() { delete m_RenderData; }
 
 	// set object transform
-	virtual void SetTransform(const CMatrix3D& transform) {
+	virtual void SetTransform(const CMatrix3D& transform)
+	{
 		if (m_Transform == transform)
 			return;
 		// store transform, calculate inverse
@@ -82,8 +84,10 @@ public:
 
 	// mark some part of the renderdata as dirty, and requiring
 	// an update on next render
-	void SetDirty(u32 dirtyflags) {
-		if (m_RenderData) m_RenderData->m_UpdateFlags|=dirtyflags;
+	void SetDirty(u32 dirtyflags)
+	{
+		if (m_RenderData)
+			m_RenderData->m_UpdateFlags |= dirtyflags;
 	}
 
 	/**
@@ -98,7 +102,8 @@ public:
 	virtual void CalcBounds() = 0;
 
 	/// Returns the world-space axis-aligned bounds of this object.
-	const CBoundingBoxAligned& GetWorldBounds() {
+	const CBoundingBoxAligned& GetWorldBounds()
+	{
 		RecalculateBoundsIfNecessary();
 		return m_WorldBounds;
 	}
@@ -111,7 +116,8 @@ public:
 	virtual void InvalidateBounds() { m_BoundsValid = false; }
 
 	// Set the object renderdata and free previous renderdata, if any.
-	void SetRenderData(CRenderData* renderdata) {
+	void SetRenderData(CRenderData* renderdata)
+	{
 		delete m_RenderData;
 		m_RenderData = renderdata;
 	}
diff --git a/source/graphics/ShaderProgram.h b/source/graphics/ShaderProgram.h
index 4f94da1..c024299 100644
--- a/source/graphics/ShaderProgram.h
+++ b/source/graphics/ShaderProgram.h
@@ -136,7 +136,7 @@ public:
 
 	/**
 	 * Returns bitset of STREAM_* value, indicating what vertex data streams the
-	 * vertex shader needs.
+	 * vertex shader needs (e.g. position, color, UV, ...).
 	 */
 	int GetStreamFlags() const;
 
diff --git a/source/graphics/ShaderProgramFFP.cpp b/source/graphics/ShaderProgramFFP.cpp
index 7eb565c..de4f8d8 100644
--- a/source/graphics/ShaderProgramFFP.cpp
+++ b/source/graphics/ShaderProgramFFP.cpp
@@ -168,10 +168,11 @@ class CShaderProgramFFP_OverlayLine : public CShaderProgramFFP
 	};
 
 	bool m_IgnoreLos;
+	bool m_UseObjectColor;
 
 public:
 	CShaderProgramFFP_OverlayLine(const CShaderDefines& defines) :
-		CShaderProgramFFP(STREAM_POS | STREAM_UV0 | STREAM_UV1)
+		CShaderProgramFFP(0) // will be set manually in initializer below
 	{
 		SetUniformIndex("losTransform", ID_losTransform);
 		SetUniformIndex("objectColor", ID_objectColor);
@@ -182,6 +183,11 @@ public:
 		SetUniformIndex("losTex", 2);
 
 		m_IgnoreLos = (defines.GetInt("IGNORE_LOS") != 0);
+		m_UseObjectColor = (defines.GetInt("USE_OBJECTCOLOR") != 0);
+
+		m_StreamFlags = STREAM_POS | STREAM_UV0 | STREAM_UV1;
+		if (!m_UseObjectColor)
+			m_StreamFlags |= STREAM_COLOR;
 	}
 
 	bool IsIgnoreLos()
@@ -219,11 +225,11 @@ public:
 	virtual void Bind()
 	{
 		// RGB channels:
-		//   Unit 0: Load base texture
-		//   Unit 1: Load mask texture; interpolate with objectColor & base
+		//   Unit 0: Sample base texture
+		//   Unit 1: Sample mask texture; interpolate with [objectColor or vertexColor] and base, depending on USE_OBJECTCOLOR
 		//   Unit 2: (Load LOS texture; multiply) if not #IGNORE_LOS, pass through otherwise
 		// Alpha channel:
-		//   Unit 0: Load base texture
+		//   Unit 0: Sample base texture
 		//   Unit 1: Multiply by objectColor
 		//   Unit 2: Pass through
 
@@ -231,10 +237,12 @@ public:
 		glEnable(GL_TEXTURE_2D);
 		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
 
+		// Sample base texture RGB
 		glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
 		glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE);
 		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
 
+		// Sample base texture Alpha
 		glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
 		glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
 		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
@@ -244,20 +252,22 @@ public:
 		pglActiveTextureARB(GL_TEXTURE1);
 		glEnable(GL_TEXTURE_2D);
 		glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
-		// Uniform() sets GL_TEXTURE_ENV_COLOR
 
-		// load mask texture; interpolate with objectColor and base; GL_INTERPOLATE takes 3 arguments:
-		// a0 * a2 + a1 * (1 - a2)
+		// RGB: interpolate component-wise between [objectColor or vertexColor] and base using mask:
+		//   a0 * a2 + a1 * (1 - a2)
+		// Overridden implementation of Uniform() sets GL_TEXTURE_ENV_COLOR to objectColor, which
+		// is referenced as GL_CONSTANT (see spec)
 		glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_INTERPOLATE);
-		glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_CONSTANT);
+		glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, m_UseObjectColor ? GL_CONSTANT : GL_PRIMARY_COLOR);
 		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
 		glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS);
 		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR);
 		glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB_ARB, GL_TEXTURE);
 		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB_ARB, GL_SRC_COLOR);
 
+		// ALPHA: Multiply base alpha with [objectColor or vertexColor] alpha
 		glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE);
-		glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_CONSTANT);
+		glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, m_UseObjectColor ? GL_CONSTANT : GL_PRIMARY_COLOR);
 		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
 		glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PREVIOUS);
 		glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA);
@@ -278,12 +288,12 @@ public:
 		}
 		else
 		{
-			// multiply RGB with LoS texture alpha channel
+			// Multiply RGB result up till now with LoS texture alpha channel
 			glEnable(GL_TEXTURE_GEN_S);
 			glEnable(GL_TEXTURE_GEN_T);
 			glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
 			glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
-			// Uniform() sets GL_OBJECT_PLANE values
+			// Overridden implementation of Uniform() sets GL_OBJECT_PLANE values
 			
 			glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
 			glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
diff --git a/source/maths/Ease.h b/source/maths/Ease.h
new file mode 100644
index 0000000..66e7859
--- /dev/null
+++ b/source/maths/Ease.h
@@ -0,0 +1,131 @@
+/* Copyright (C) 2012 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef INCLUDED_EASE
+#define INCLUDED_EASE
+
+/**
+ * Straightforward C++ port of Robert Penner's easing equations
+ * http://www.robertpenner.com/easing/
+ *
+ * Open source under the BSD License.
+ * Copyright (c) 2001 Robert Penner
+ * All rights reserved.
+ * http://www.robertpenner.com/easing_terms_of_use.html
+ */
+
+#include <math.h>
+
+/**
+ * Generic easing functions. In each function, the parameters are:
+ * 
+ * @param t Current time in seconds, as a float between 0 and d (inclusive).
+ * @param d Total duration of the ease, in seconds. Must be strictly positive.
+ * @param b Baseline value (at t = 0).
+ * @param c Delta from baseline value to reach the target value (at t = 1). I.e., target = b + c.
+ * 
+ * Each function outputs the eased value between 'b' and 'b+c' at time 't'.
+ */
+class Ease
+{
+public:
+	static float QuadIn(float t, const float b, const float c, const float d)
+	{
+		t /= d;
+		return c*t*t + b;
+	}
+
+	static float QuadOut(float t, const float b, const float c, const float d)
+	{
+		t /= d;
+		return -c * t*(t-2) + b;
+	}
+
+	static float QuadInOut(float t, const float b, const float c, const float d)
+	{
+		t /= d/2;
+		if (t < 1)
+			return c/2*t*t + b;
+		--t;
+		return -c/2 * (t*(t-2) - 1) + b;
+	}
+
+	static float CubicIn(float t, const float b, const float c, const float d)
+	{
+		t /= d;
+		return c*t*t*t + b;
+	}
+
+	static float CubicOut(float t, const float b, const float c, const float d)
+	{
+		t = t/d - 1;
+		return c*(t*t*t + 1) + b;
+	}
+
+	static float CubicInOut(float t, const float b, const float c, const float d)
+	{
+		t /= d/2;
+		if (t < 1)
+			return c/2*t*t*t + b;
+		t -= 2;
+		return c/2*(t*t*t + 2) + b;
+	}
+
+	static float QuartIn(float t, const float b, const float c, const float d)
+	{
+		t /= d;
+		return c*t*t*t*t + b;
+	}
+
+	static float QuartOut(float t, const float b, const float c, const float d)
+	{
+		t = t/d - 1;
+		return -c*(t*t*t*t - 1) + b;
+	}
+
+	static float QuartInOut(float t, const float b, const float c, const float d)
+	{
+		t /= d/2;
+		if (t < 1)
+			return c/2*t*t*t*t + b;
+		t -= 2;
+		return -c/2 * (t*t*t*t - 2) + b;
+	}
+
+	static float QuintIn(float t, const float b, const float c, const float d)
+	{
+		t /= d;
+		return c*t*t*t*t*t + b;
+	}
+
+	static float QuintOut(float t, const float b, const float c, const float d)
+	{
+		t = t/d - 1;
+		return c*(t*t*t*t*t + 1) + b;
+	}
+
+	static float QuintInOut(float t, const float b, const float c, const float d)
+	{
+		t /= d/2;
+		if (t < 1)
+			return c/2*t*t*t*t*t + b;
+		t -= 2;
+		return c/2*(t*t*t*t*t + 2) + b;
+	}
+};
+
+#endif // INCLUDED_EASE
diff --git a/source/maths/FixedVector2D.h b/source/maths/FixedVector2D.h
index c58f66f..739469e 100644
--- a/source/maths/FixedVector2D.h
+++ b/source/maths/FixedVector2D.h
@@ -27,7 +27,6 @@ public:
 	fixed X, Y;
 
 	CFixedVector2D() { }
-
 	CFixedVector2D(fixed X, fixed Y) : X(X), Y(Y) { }
 
 	/// Vector equality
diff --git a/source/maths/MathUtil.h b/source/maths/MathUtil.h
index 741e57e..b00a70b 100644
--- a/source/maths/MathUtil.h
+++ b/source/maths/MathUtil.h
@@ -18,6 +18,8 @@
 #ifndef INCLUDED_MATHUTIL
 #define INCLUDED_MATHUTIL
 
+#include <cmath>
+
 #define DEGTORAD(a)					((a) * ((float)M_PI/180.0f))
 #define RADTODEG(a)					((a) * (180.0f/(float)M_PI))
 #define SQR(x)						((x) * (x))
diff --git a/source/maths/Vector2D.h b/source/maths/Vector2D.h
index 977584c..55787d8 100644
--- a/source/maths/Vector2D.h
+++ b/source/maths/Vector2D.h
@@ -121,7 +121,7 @@ public:
 		Y /= mag;
 	}
 
-	CVector2D Normalized()
+	CVector2D Normalized() const
 	{
 		float mag = Length();
 		return CVector2D(X / mag, Y / mag);
@@ -130,7 +130,7 @@ public:
 	/**
 	 * Returns a version of this vector rotated counterclockwise by @p angle radians.
 	 */
-	CVector2D Rotated(float angle)
+	CVector2D Rotated(float angle) const
 	{
 		float c = cosf(angle);
 		float s = sinf(angle);
diff --git a/source/renderer/OverlayRenderer.cpp b/source/renderer/OverlayRenderer.cpp
index 0a30e7a..c22e8e8 100644
--- a/source/renderer/OverlayRenderer.cpp
+++ b/source/renderer/OverlayRenderer.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -19,34 +19,151 @@
 
 #include "OverlayRenderer.h"
 
-#include "maths/MathUtil.h"
-#include "maths/Quaternion.h"
-#include "maths/Vector2D.h"
+#include <boost/unordered_map.hpp>
 #include "graphics/LOSTexture.h"
 #include "graphics/Overlay.h"
 #include "graphics/Terrain.h"
 #include "graphics/TextureManager.h"
 #include "lib/ogl.h"
+#include "maths/MathUtil.h"
+#include "maths/Quaternion.h"
 #include "ps/Game.h"
 #include "ps/Profile.h"
 #include "renderer/Renderer.h"
+#include "renderer/VertexArray.h"
 #include "renderer/VertexBuffer.h"
 #include "renderer/VertexBufferManager.h"
 #include "simulation2/Simulation2.h"
 #include "simulation2/components/ICmpWaterManager.h"
+#include "simulation2/system/SimContext.h"
+
+/**
+ * As a general TODO, some of the code here still uses g_VBMan manually.
+ * For consistency with other parts of the engine, it'd be nice to switch
+ * over to the cleaner and more readable VertexArray API.
+ */
+
+const float OverlayRenderer::OVERLAY_VOFFSET = 0.2f;
+
+/**
+ * Key used to group quads into batches for more efficient rendering. Currently groups by the combination
+ * of the main texture and the texture mask, to minimize texture swapping during rendering.
+ */
+struct QuadBatchKey
+{
+	QuadBatchKey (const CTexturePtr& texture, const CTexturePtr& textureMask)
+		: m_Texture(texture), m_TextureMask(textureMask)
+	{ }
+
+	bool operator==(const QuadBatchKey& other) const
+	{
+		return (m_Texture == other.m_Texture && m_TextureMask == other.m_TextureMask);
+	}
+
+	CTexturePtr m_Texture;
+	CTexturePtr m_TextureMask;
+};
+
+/**
+ * Holds information about a single quad rendering batch.
+ */
+class QuadBatchData : public CRenderData
+{
+public:
+	QuadBatchData() : m_IndicesBase(0) { }
+
+	/// Holds the quad overlay structures to be rendered during this batch.
+	/// Must be cleared after each frame.
+	std::vector<SOverlayQuad*> m_Quads;
+	/// Start index of this batch into the dedicated quad indices VertexArray (see OverlayInternals).
+	size_t m_IndicesBase;
+};
 
 struct OverlayRendererInternals
 {
+	typedef boost::unordered_map<QuadBatchKey, QuadBatchData> QuadBatchMap;
+
+	OverlayRendererInternals();
+	~OverlayRendererInternals(){ }
+
 	std::vector<SOverlayLine*> lines;
 	std::vector<SOverlayTexturedLine*> texlines;
 	std::vector<SOverlaySprite*> sprites;
+	std::vector<SOverlayQuad*> quads;
+
+	QuadBatchMap quadBatchMap;
+
+	// Dedicated vertex/index buffers for rendering all quads (to within the limits set by
+	// MAX_QUAD_OVERLAYS).
+	VertexArray quadVertices;
+	VertexArray::Attribute quadAttributePos;
+	VertexArray::Attribute quadAttributeColor;
+	VertexArray::Attribute quadAttributeUV;
+	VertexIndexArray quadIndices;
+
+	/// Maximum amount of quad overlays we support for rendering. This limit is set to be able to 
+	/// render all quads from a single dedicated VB without having to reallocate it, which is much
+	/// faster in the typical case of rendering only a handful of quads. When modifying this value,
+	/// you must take care for the new amount of quads to fit in a single VBO (which is not likely
+	/// to be a problem).
+	static const size_t MAX_QUAD_OVERLAYS = 1024;
+
+	// Sets of commonly-(re)used shader defines.
+	CShaderDefines defsOverlayLineNormal;
+	CShaderDefines defsOverlayLineAlwaysVisible;
+	CShaderDefines defsQuadOverlay;
 };
 
+OverlayRendererInternals::OverlayRendererInternals()
+	: quadVertices(GL_DYNAMIC_DRAW), quadIndices(GL_DYNAMIC_DRAW)
+{
+	quadAttributePos.elems = 3;
+	quadAttributePos.type = GL_FLOAT;
+	quadVertices.AddAttribute(&quadAttributePos);
+
+	quadAttributeColor.elems = 4;
+	quadAttributeColor.type = GL_FLOAT;
+	quadVertices.AddAttribute(&quadAttributeColor);
+
+	quadAttributeUV.elems = 2;
+	quadAttributeUV.type = GL_SHORT; // don't use GL_UNSIGNED_SHORT here, TexCoordPointer won't accept it
+	quadVertices.AddAttribute(&quadAttributeUV);
+
+	quadVertices.SetNumVertices(MAX_QUAD_OVERLAYS * 4);
+	quadVertices.Layout(); // allocate backing store
+
+	quadIndices.SetNumVertices(MAX_QUAD_OVERLAYS * 6);
+	quadIndices.Layout(); // allocate backing store
+
+	// Since the quads in the vertex array are independent and always consist of exactly 4 vertices per quad, the
+	// indices are always the same; we can therefore fill in all the indices once and pretty much forget about
+	// them. We then also no longer need its backing store, since we never change any indices afterwards.
+	VertexArrayIterator<u16> index = quadIndices.GetIterator();
+	for (size_t i = 0; i < MAX_QUAD_OVERLAYS; ++i)
+	{
+		*index++ = i*4 + 0;
+		*index++ = i*4 + 1;
+		*index++ = i*4 + 2;
+		*index++ = i*4 + 2;
+		*index++ = i*4 + 3;
+		*index++ = i*4 + 0;
+	}
+	quadIndices.Upload();
+	quadIndices.FreeBackingStore();
+
+	// Note that we're reusing the textured overlay line shader for the quad overlay rendering. This
+	// is because their code is almost identical; the only difference is that for the quad overlays
+	// we want to use a vertex color stream as opposed to an objectColor uniform. To this end, the
+	// shader has been set up to switch between the two behaviours based on the USE_OBJECTCOLOR define.
+	defsOverlayLineNormal.Add("USE_OBJECTCOLOR", "1");
+	defsOverlayLineAlwaysVisible.Add("USE_OBJECTCOLOR", "1");
+	defsOverlayLineAlwaysVisible.Add("IGNORE_LOS", "1");
+}
+
 class CTexturedLineRData : public CRenderData
 {
 public:
-	CTexturedLineRData(SOverlayTexturedLine* line) :
-		m_Line(line), m_VB(NULL), m_VBIndices(NULL), m_Raise(.2f)
+	CTexturedLineRData(SOverlayTexturedLine* line) : m_Line(line), m_VB(NULL), m_VBIndices(NULL)
 	{ }
 
 	~CTexturedLineRData()
@@ -62,7 +179,7 @@ public:
 		SVertex(CVector3D pos, float u, float v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; }
 		CVector3D m_Position;
 		GLfloat m_UVs[2];
-		float _padding[3]; // 5 floats up till now, so pad with another 3 floats to get a power of 2
+		float _padding[3]; // get a pow2 struct size
 	};
 	cassert(sizeof(SVertex) == 32);
 
@@ -91,10 +208,16 @@ public:
 	SOverlayTexturedLine* m_Line;
 	CVertexBuffer::VBChunk* m_VB;
 	CVertexBuffer::VBChunk* m_VBIndices;
-
-	float m_Raise; // small vertical offset of line from terrain to prevent visual glitches
 };
 
+static size_t hash_value(const QuadBatchKey& d)
+{
+	size_t seed = 0;
+	boost::hash_combine(seed, d.m_Texture);
+	boost::hash_combine(seed, d.m_TextureMask);
+	return seed;
+}
+
 OverlayRenderer::OverlayRenderer()
 {
 	m = new OverlayRendererInternals();
@@ -128,13 +251,25 @@ void OverlayRenderer::Submit(SOverlaySprite* overlay)
 	m->sprites.push_back(overlay);
 }
 
+void OverlayRenderer::Submit(SOverlayQuad* overlay)
+{
+	m->quads.push_back(overlay);
+}
+
 void OverlayRenderer::EndFrame()
 {
 	m->lines.clear();
 	m->texlines.clear();
 	m->sprites.clear();
+	m->quads.clear();
 	// this should leave the capacity unchanged, which is okay since it
 	// won't be very large or very variable
+	
+	// Empty the batch rendering data structures, but keep their key mappings around for the next frames
+	for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); it++)
+	{
+		it->second.m_Quads.clear();
+	}
 }
 
 void OverlayRenderer::PrepareForRendering()
@@ -157,6 +292,75 @@ void OverlayRenderer::PrepareForRendering()
 			// any of the parameters after first submitting the line.
 		}
 	}
+
+	// Group quad overlays by their texture/mask combination for efficient rendering
+	for (size_t i = 0; i < m->quads.size(); ++i)
+	{
+		SOverlayQuad* const quad = m->quads[i];
+
+		QuadBatchKey textures(quad->m_Texture, quad->m_TextureMask);
+		QuadBatchData& batchRenderData = m->quadBatchMap[textures]; // will create entry if it doesn't already exist
+
+		// add overlay to list of quads
+		batchRenderData.m_Quads.push_back(quad);
+	}
+
+	const CVector3D vOffset(0, OVERLAY_VOFFSET, 0);
+
+	// Write quad overlay vertices/indices to VA backing store
+	VertexArrayIterator<CVector3D> vertexPos = m->quadAttributePos.GetIterator<CVector3D>();
+	VertexArrayIterator<CVector4D> vertexColor = m->quadAttributeColor.GetIterator<CVector4D>();
+	VertexArrayIterator<short[2]> vertexUV = m->quadAttributeUV.GetIterator<short[2]>();
+
+	size_t indicesIdx = 0;
+
+	for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it)
+	{
+		QuadBatchData& batchRenderData = (it->second);
+		if (batchRenderData.m_Quads.empty())
+			continue;
+
+		// Remember our current index into the (entire) indices array as our base offset for this batch
+		batchRenderData.m_IndicesBase = indicesIdx;
+
+		// points to the index where each iteration's vertices will be appended
+		for (size_t i = 0; i < batchRenderData.m_Quads.size(); i++)
+		{
+			const SOverlayQuad* quad = batchRenderData.m_Quads[i];
+
+			// TODO: this is kind of ugly, the iterator should use a type that can have quad->m_Color assigned
+			// to it directly
+			const CVector4D quadColor(quad->m_Color.r, quad->m_Color.g, quad->m_Color.b, quad->m_Color.a);
+
+			*vertexPos++ = quad->m_Corners[0] + vOffset;
+			*vertexPos++ = quad->m_Corners[1] + vOffset;
+			*vertexPos++ = quad->m_Corners[2] + vOffset;
+			*vertexPos++ = quad->m_Corners[3] + vOffset;
+			
+			(*vertexUV)[0] = 0;
+			(*vertexUV)[1] = 0;
+			++vertexUV;
+			(*vertexUV)[0] = 0;
+			(*vertexUV)[1] = 1;
+			++vertexUV;
+			(*vertexUV)[0] = 1;
+			(*vertexUV)[1] = 1;
+			++vertexUV;
+			(*vertexUV)[0] = 1;
+			(*vertexUV)[1] = 0;
+			++vertexUV;
+
+			*vertexColor++ = quadColor;
+			*vertexColor++ = quadColor;
+			*vertexColor++ = quadColor;
+			*vertexColor++ = quadColor;
+
+			indicesIdx += 6;
+		}
+	}
+
+	m->quadVertices.Upload();
+	// don't free the backing store! we'll overwrite it on the next frame to save a reallocation.
 }
 
 void OverlayRenderer::RenderOverlaysBeforeWater()
@@ -196,36 +400,41 @@ void OverlayRenderer::RenderOverlaysAfterWater()
 {
 	PROFILE3_GPU("overlays (after)");
 
+	RenderTexturedOverlayLines();
+	RenderQuadOverlays();
+}
+
+void OverlayRenderer::RenderTexturedOverlayLines()
+{
 #if CONFIG2_GLES
-#warning TODO: implement OverlayRenderer::RenderOverlaysAfterWater for GLES
+#warning TODO: implement OverlayRenderer::RenderTexturedOverlayLines for GLES
 	return;
 #endif
+	if (m->texlines.empty())
+		return;
 
 	ogl_WarnIfError();
 
-	if (!m->texlines.empty())
-	{
-		glEnable(GL_TEXTURE_2D);
-		glEnable(GL_BLEND);
-		glDepthMask(0);
-
-		const char* shaderName;
-		if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER)
-			shaderName = "arb/overlayline";
-		else
-			shaderName = "fixed:overlayline";
+	glEnable(GL_TEXTURE_2D);
+	glEnable(GL_BLEND);
+	glDepthMask(0);
 
-		CShaderDefines defAlwaysVisible;
-		defAlwaysVisible.Add("IGNORE_LOS", "1");
+	const char* shaderName;
+	if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER)
+		shaderName = "arb/overlayline";
+	else
+		shaderName = "fixed:overlayline";
 
-		CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture();
+	CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture();
 
-		CShaderManager& shaderManager = g_Renderer.GetShaderManager();
-		CShaderProgramPtr shaderTexLineNormal(shaderManager.LoadProgram(shaderName, CShaderDefines()));
-		CShaderProgramPtr shaderTexLineAlwaysVisible(shaderManager.LoadProgram(shaderName, defAlwaysVisible));
+	CShaderManager& shaderManager = g_Renderer.GetShaderManager();
+	CShaderProgramPtr shaderTexLineNormal(shaderManager.LoadProgram(shaderName, m->defsOverlayLineNormal));
+	CShaderProgramPtr shaderTexLineAlwaysVisible(shaderManager.LoadProgram(shaderName, m->defsOverlayLineAlwaysVisible));
 
-		// ----------------------------------------------------------------------------------------
+	// ----------------------------------------------------------------------------------------
 
+	if (shaderTexLineNormal)
+	{
 		shaderTexLineNormal->Bind();
 		shaderTexLineNormal->BindTexture("losTex", los.GetTexture());
 		shaderTexLineNormal->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
@@ -234,12 +443,14 @@ void OverlayRenderer::RenderOverlaysAfterWater()
 		RenderTexturedOverlayLines(shaderTexLineNormal, false);
 
 		shaderTexLineNormal->Unbind();
+	}
 
-		// ----------------------------------------------------------------------------------------
+	// ----------------------------------------------------------------------------------------
 
+	if (shaderTexLineAlwaysVisible)
+	{
 		shaderTexLineAlwaysVisible->Bind();
-		// TODO: losTex and losTransform are unused in the always visible shader, but I'm not sure if it's worthwhile messing 
-		// with it just to remove these calls
+		// TODO: losTex and losTransform are unused in the always visible shader; see if these can be safely omitted
 		shaderTexLineAlwaysVisible->BindTexture("losTex", los.GetTexture());
 		shaderTexLineAlwaysVisible->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
 
@@ -247,16 +458,18 @@ void OverlayRenderer::RenderOverlaysAfterWater()
 		RenderTexturedOverlayLines(shaderTexLineAlwaysVisible, true);
 
 		shaderTexLineAlwaysVisible->Unbind();
+	}
 
-		// TODO: the shader should probably be responsible for unbinding its textures
-		g_Renderer.BindTexture(1, 0);
-		g_Renderer.BindTexture(0, 0);
+	// ----------------------------------------------------------------------------------------
 
-		CVertexBuffer::Unbind();
+	// TODO: the shaders should probably be responsible for unbinding their textures
+	g_Renderer.BindTexture(1, 0);
+	g_Renderer.BindTexture(0, 0);
 
-		glDepthMask(1);
-		glDisable(GL_BLEND);
-	}
+	CVertexBuffer::Unbind();
+
+	glDepthMask(1);
+	glDisable(GL_BLEND);
 }
 
 void OverlayRenderer::RenderTexturedOverlayLines(CShaderProgramPtr shaderTexLine, bool alwaysVisible)
@@ -298,10 +511,101 @@ void OverlayRenderer::RenderTexturedOverlayLines(CShaderProgramPtr shaderTexLine
 		shaderTexLine->AssertPointersBound();
 		glDrawElements(GL_TRIANGLES, rdata->m_VBIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index); 
 
+		g_Renderer.GetStats().m_DrawCalls++;
 		g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count/3; 
 	}
 }
 
+void OverlayRenderer::RenderQuadOverlays()
+{
+	if (m->quadBatchMap.empty())
+		return;
+
+	ogl_WarnIfError();
+
+	glEnable(GL_TEXTURE_2D);
+	glEnable(GL_BLEND);
+	glDepthMask(0);
+
+	const char* shaderName;
+	if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER)
+		shaderName = "arb/overlayline";
+	else
+		shaderName = "fixed:overlayline";
+
+	// TODO: create an FFP version of this shader?
+
+	CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture();
+
+	CShaderManager& shaderManager = g_Renderer.GetShaderManager();
+	CShaderProgramPtr shader(shaderManager.LoadProgram(shaderName, m->defsQuadOverlay));
+
+	// ----------------------------------------------------------------------------------------
+
+	if (shader)
+	{
+		shader->Bind();
+		shader->BindTexture("losTex", los.GetTexture());
+		shader->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
+
+		// Base offsets (in bytes) of the two backing stores relative to their owner VBO
+		// (the backing stores are chunks inside a larger, bound VBO)
+		u8* indexBase = m->quadIndices.Bind();
+		u8* vertexBase = m->quadVertices.Bind();
+		GLsizei indexStride = m->quadIndices.GetStride();
+		GLsizei vertexStride = m->quadVertices.GetStride();
+
+		for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); it++)
+		{
+			QuadBatchData& batchRenderData = it->second;
+			const size_t batchNumQuads = batchRenderData.m_Quads.size();
+
+			// Careful; some drivers don't like drawing calls with 0 stuff to draw.
+			// Also needed to ensure that batchRenderData.m_IndicesBase is valid (see PrepareForRendering).
+			if (batchNumQuads == 0)
+				continue;
+
+			const QuadBatchKey& maskPair = it->first;
+
+			shader->BindTexture("baseTex", maskPair.m_Texture->GetHandle());
+			shader->BindTexture("maskTex", maskPair.m_TextureMask->GetHandle());
+
+			int streamflags = shader->GetStreamFlags(); ogl_WarnIfError();
+
+			if (streamflags & STREAM_POS)
+				shader->VertexPointer(m->quadAttributePos.elems, m->quadAttributePos.type, vertexStride, vertexBase + m->quadAttributePos.offset);
+
+			if (streamflags & STREAM_UV0)
+				shader->TexCoordPointer(GL_TEXTURE0, m->quadAttributeUV.elems, m->quadAttributeUV.type, vertexStride, vertexBase + m->quadAttributeUV.offset);
+
+			if (streamflags & STREAM_UV1)
+				shader->TexCoordPointer(GL_TEXTURE1, m->quadAttributeUV.elems, m->quadAttributeUV.type, vertexStride, vertexBase + m->quadAttributeUV.offset);
+			
+			if (streamflags & STREAM_COLOR)
+				shader->ColorPointer(m->quadAttributeColor.elems, m->quadAttributeColor.type, vertexStride, vertexBase + m->quadAttributeColor.offset);
+			
+			shader->AssertPointersBound();
+			glDrawElements(GL_TRIANGLES, (GLsizei)(batchNumQuads * 6), GL_UNSIGNED_SHORT, indexBase + indexStride * batchRenderData.m_IndicesBase);
+
+			g_Renderer.GetStats().m_DrawCalls++;
+			g_Renderer.GetStats().m_OverlayTris += batchNumQuads*2;
+		}
+
+		shader->Unbind();
+	}
+
+	// ----------------------------------------------------------------------------------------
+
+	// TODO: the shader should probably be responsible for unbinding its textures
+	g_Renderer.BindTexture(1, 0);
+	g_Renderer.BindTexture(0, 0);
+
+	CVertexBuffer::Unbind();
+
+	glDepthMask(1);
+	glDisable(GL_BLEND);
+}
+
 void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera)
 {
 	PROFILE3_GPU("overlays (fg)");
@@ -339,6 +643,9 @@ void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera)
 
 		glVertexPointer(3, GL_FLOAT, sizeof(float)*3, &pos[0].X);
 		glDrawArrays(GL_QUADS, 0, (GLsizei)4);
+
+		g_Renderer.GetStats().m_DrawCalls++;
+		g_Renderer.GetStats().m_OverlayTris += 2;
 	}
 
 	glDisableClientState(GL_VERTEX_ARRAY);
@@ -363,8 +670,14 @@ void CTexturedLineRData::Update()
 		m_VBIndices = NULL;
 	}
 
-	CTerrain* terrain = m_Line->m_Terrain;
-	CmpPtr<ICmpWaterManager> cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
+	if (!m_Line->m_SimContext)
+	{
+		debug_warn(L"[OverlayRenderer] No SimContext set for textured overlay line, cannot render (no terrain data)");
+		return;
+	}
+
+	const CTerrain& terrain = m_Line->m_SimContext->GetTerrain();
+	CmpPtr<ICmpWaterManager> cmpWaterManager(*m_Line->m_SimContext, SYSTEM_ENTITY);
 
 	float v = 0.f;
 	std::vector<SVertex> vertices;
@@ -400,18 +713,18 @@ void CTexturedLineRData::Update()
 	// TODO: if we ever support more than one water level per map, recompute this per point
 	float w = cmpWaterManager->GetExactWaterLevel(p0.X, p0.Z);
 
-	p0.Y = terrain->GetExactGroundLevel(p0.X, p0.Z);
+	p0.Y = terrain.GetExactGroundLevel(p0.X, p0.Z);
 	if (p0.Y < w)
 		p0.Y = w;
 
-	p1.Y = terrain->GetExactGroundLevel(p1.X, p1.Z);
+	p1.Y = terrain.GetExactGroundLevel(p1.X, p1.Z);
 	if (p1.Y < w)
 	{
 		p1.Y = w;
 		p1floating = true;
 	}
 
-	p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z);
+	p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z);
 	if (p2.Y < w)
 	{
 		p2.Y = w;
@@ -428,7 +741,7 @@ void CTexturedLineRData::Update()
 		if (p1floating)
 			norm = CVector3D(0, 1, 0);
 		else
-			norm = m_Line->m_Terrain->CalcExactNormal(p1.X, p1.Z);
+			norm = terrain.CalcExactNormal(p1.X, p1.Z);
 
 		CVector3D b = ((p1 - p0).Normalized() + (p2 - p1).Normalized()).Cross(norm);
 
@@ -447,8 +760,8 @@ void CTexturedLineRData::Update()
 		// What the code below does is push the indices for a quad composed of two triangles in each iteration. The two triangles 
 		// of each quad are indexed using the winding orders (BR, BL, TR) and (TR, BL, TR) (where BR is bottom-right of this
 		// iteration's quad, TR top-right etc).
-		SVertex vertex1(p1 + b + norm*m_Raise, 0.f, v);
-		SVertex vertex2(p1 - b + norm*m_Raise, 1.f, v);
+		SVertex vertex1(p1 + b + norm*OverlayRenderer::OVERLAY_VOFFSET, 0.f, v);
+		SVertex vertex2(p1 - b + norm*OverlayRenderer::OVERLAY_VOFFSET, 1.f, v);
 		vertices.push_back(vertex1);
 		vertices.push_back(vertex2);
 
@@ -497,7 +810,7 @@ void CTexturedLineRData::Update()
 		else
 			p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]);
 
-		p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z);
+		p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z);
 		if (p2.Y < w)
 		{
 			p2.Y = w;
@@ -540,6 +853,7 @@ void CTexturedLineRData::Update()
 
 		for (unsigned i = 0; i < capIndices.size(); i++)
 			capIndices[i] += vertices.size();
+
 		vertices.insert(vertices.end(), capVertices.begin(), capVertices.end());
 		indices.insert(indices.end(), capIndices.begin(), capIndices.end());
 
@@ -560,6 +874,7 @@ void CTexturedLineRData::Update()
 
 		for (unsigned i = 0; i < capIndices.size(); i++)
 			capIndices[i] += vertices.size();
+
 		vertices.insert(vertices.end(), capVertices.begin(), capVertices.end());
 		indices.insert(indices.end(), capIndices.begin(), capIndices.end());
 	}
@@ -595,10 +910,10 @@ void CTexturedLineRData::CreateLineCap(const CVector3D& corner1, const CVector3D
 	// That is to say, when viewed from the top, we will have something like
 	//                                                 .
 	//  this:                     and not like this:  /|
-	//         ____.                                 / |
+	//         ----+                                 / |
 	//             |                                /  .
 	//             |                                  /
-	//         ____.                                 /
+	//         ----+                                 /
 	//
 
 	int roundCapPoints = 8; // amount of points to sample along the semicircle for rounded caps (including corner points)
diff --git a/source/renderer/OverlayRenderer.h b/source/renderer/OverlayRenderer.h
index 6c1dfdc..a61656f 100644
--- a/source/renderer/OverlayRenderer.h
+++ b/source/renderer/OverlayRenderer.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -23,6 +23,7 @@
 struct SOverlayLine;
 struct SOverlayTexturedLine;
 struct SOverlaySprite;
+struct SOverlayQuad;
 class CCamera;
 
 struct OverlayRendererInternals;
@@ -39,20 +40,33 @@ public:
 
 	/**
 	 * Add a line overlay for rendering in this frame.
+	 * @param overlay Must be non-null. The pointed-to object must remain valid at least
+	 *                until the end of the frame.
 	 */
 	void Submit(SOverlayLine* overlay);
 
 	/**
 	 * Add a textured line overlay for rendering in this frame.
+	 * @param overlay Must be non-null. The pointed-to object must remain valid at least
+	 *                until the end of the frame.
 	 */
 	void Submit(SOverlayTexturedLine* overlay);
 
 	/**
 	 * Add a sprite overlay for rendering in this frame.
+	 * @param overlay Must be non-null. The pointed-to object must remain valid at least
+	 *                until the end of the frame.
 	 */
 	void Submit(SOverlaySprite* overlay);
 
 	/**
+	 * Add a textured quad overlay for rendering in this frame.
+	 * @param overlay Must be non-null. The pointed-to object must remain valid at least
+	 *                until the end of the frame.
+	 */
+	void Submit(SOverlayQuad* overlay);
+
+	/**
 	 * Prepare internal data structures for rendering.
 	 * Must be called after all Submit calls for a frame, and before
 	 * any rendering calls.
@@ -85,15 +99,31 @@ public:
 	 */
 	void RenderForegroundOverlays(const CCamera& viewCamera);
 
+	/// Small vertical offset of overlays from terrain to prevent visual glitches
+	static const float OVERLAY_VOFFSET;
+
 private:
 	
 	/**
-	 * Helper method; renders those overlay lines currently registered in the internals (i.e. in m->texlines) for which the
-	 * always visible flag equals @alwaysVisible. Used for batch rendering the overlay lines by their alwaysVisible status,
-	 * because this requires a separate shader to be used.
+	 * Helper method; renders all overlay lines currently registered in the internals. Batch-
+	 * renders textured overlay lines batched according to their visibility status by delegating
+	 * to RenderTexturedOverlayLines(CShaderProgramPtr, bool).
+	 */
+	void RenderTexturedOverlayLines();
+
+	/**
+	 * Helper method; renders those overlay lines currently registered in the internals (i.e.
+	 * in m->texlines) for which the 'always visible' flag equals @p alwaysVisible. Used for
+	 * batch rendering the overlay lines according to their alwaysVisible status, as this
+	 * requires a separate shader to be used.
 	 */
 	void RenderTexturedOverlayLines(CShaderProgramPtr shader, bool alwaysVisible);
 
+	/**
+	 * Helper method; batch-renders all registered quad overlays, batched by their texture for effiency.
+	 */
+	void RenderQuadOverlays();
+
 private:
 	OverlayRendererInternals* m;
 };
diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp
index d743ef2..e66c1fa 100644
--- a/source/renderer/Renderer.cpp
+++ b/source/renderer/Renderer.cpp
@@ -1569,6 +1569,11 @@ void CRenderer::Submit(SOverlaySprite* overlay)
 	m->overlayRenderer.Submit(overlay);
 }
 
+void CRenderer::Submit(SOverlayQuad* overlay)
+{
+	m->overlayRenderer.Submit(overlay);
+}
+
 void CRenderer::Submit(CModelDecal* decal)
 {
 	m->terrainRenderer.Submit(decal);
diff --git a/source/renderer/Renderer.h b/source/renderer/Renderer.h
index a6d1b2b..df82579 100644
--- a/source/renderer/Renderer.h
+++ b/source/renderer/Renderer.h
@@ -316,6 +316,7 @@ protected:
 	void Submit(SOverlayLine* overlay);
 	void Submit(SOverlayTexturedLine* overlay);
 	void Submit(SOverlaySprite* overlay);
+	void Submit(SOverlayQuad* overlay);
 	void Submit(CModelDecal* decal);
 	void Submit(CParticleEmitter* emitter);
 	void SubmitNonRecursive(CModel* model);
diff --git a/source/renderer/Scene.h b/source/renderer/Scene.h
index 2018135..99c99f7 100644
--- a/source/renderer/Scene.h
+++ b/source/renderer/Scene.h
@@ -39,6 +39,7 @@ class CTerritoryTexture;
 struct SOverlayLine;
 struct SOverlayTexturedLine;
 struct SOverlaySprite;
+struct SOverlayQuad;
 
 class SceneCollector;
 
@@ -104,6 +105,11 @@ public:
 	virtual void Submit(SOverlaySprite* overlay) = 0;
 
 	/**
+	 * Submit a textured quad overlay.
+	 */
+	virtual void Submit(SOverlayQuad* overlay) = 0;
+
+	/**
 	 * Submit a terrain decal.
 	 */
 	virtual void Submit(CModelDecal* decal) = 0;
diff --git a/source/renderer/VertexArray.cpp b/source/renderer/VertexArray.cpp
index 6bf1fed..42d769b 100644
--- a/source/renderer/VertexArray.cpp
+++ b/source/renderer/VertexArray.cpp
@@ -22,6 +22,7 @@
 #include "lib/sysdep/rtl.h"
 #include "maths/Vector3D.h"
 #include "maths/Vector4D.h"
+#include "graphics/Color.h"
 #include "graphics/SColor.h"
 #include "renderer/VertexArray.h"
 #include "renderer/VertexBuffer.h"
@@ -73,7 +74,10 @@ void VertexArray::SetNumVertices(size_t num)
 // Add vertex attributes like Position, Normal, UV
 void VertexArray::AddAttribute(Attribute* attr)
 {
-	ENSURE((attr->type == GL_FLOAT || attr->type == GL_UNSIGNED_SHORT || attr->type == GL_UNSIGNED_BYTE) && "Unsupported attribute type");
+	ENSURE(
+		(attr->type == GL_FLOAT || attr->type == GL_SHORT || attr->type == GL_UNSIGNED_SHORT || attr->type == GL_UNSIGNED_BYTE)
+		&& "Unsupported attribute type"
+	);
 	ENSURE(attr->elems >= 1 && attr->elems <= 4);
 
 	attr->vertexArray = this;
@@ -147,6 +151,16 @@ VertexArrayIterator<u16> VertexArray::Attribute::GetIterator<u16>() const
 }
 
 template<>
+VertexArrayIterator<u16[2]> VertexArray::Attribute::GetIterator<u16[2]>() const
+{
+	ENSURE(vertexArray);
+	ENSURE(type == GL_UNSIGNED_SHORT);
+	ENSURE(elems >= 2);
+
+	return vertexArray->MakeIterator<u16[2]>(this);
+}
+
+template<>
 VertexArrayIterator<u8> VertexArray::Attribute::GetIterator<u8>() const
 {
 	ENSURE(vertexArray);
@@ -166,7 +180,25 @@ VertexArrayIterator<u8[4]> VertexArray::Attribute::GetIterator<u8[4]>() const
 	return vertexArray->MakeIterator<u8[4]>(this);
 }
 
+template<>
+VertexArrayIterator<short> VertexArray::Attribute::GetIterator<short>() const
+{
+	ENSURE(vertexArray);
+	ENSURE(type == GL_SHORT);
+	ENSURE(elems >= 1);
+
+	return vertexArray->MakeIterator<short>(this);
+}
+
+template<>
+VertexArrayIterator<short[2]> VertexArray::Attribute::GetIterator<short[2]>() const
+{
+	ENSURE(vertexArray);
+	ENSURE(type == GL_SHORT);
+	ENSURE(elems >= 2);
 
+	return vertexArray->MakeIterator<short[2]>(this);
+}
 
 static size_t RoundStride(size_t stride)
 {
@@ -206,6 +238,9 @@ void VertexArray::Layout()
 		case GL_UNSIGNED_BYTE:
 			attrSize = sizeof(GLubyte);
 			break;
+		case GL_SHORT:
+			attrSize = sizeof(GLshort);
+			break;
 		case GL_UNSIGNED_SHORT:
 			attrSize = sizeof(GLushort);
 			break;
diff --git a/source/renderer/VertexArray.h b/source/renderer/VertexArray.h
index 7cf6cfa..4960052 100644
--- a/source/renderer/VertexArray.h
+++ b/source/renderer/VertexArray.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -134,7 +134,7 @@ class VertexArray
 public:
 	struct Attribute
 	{
-		// Data type. Currently supported: GL_FLOAT, GL_UNSIGNED_BYTE
+		// Data type. Currently supported: GL_FLOAT, GL_SHORT, GL_UNSIGNED_SHORT, GL_UNSIGNED_BYTE.
 		GLenum type;
 		// How many elements per vertex (e.g. 3 for RGB, 2 for UV)
 		GLuint elems;
@@ -146,8 +146,10 @@ public:
 
 		Attribute() : type(0), elems(0), offset(0), vertexArray(0) { }
 	
-		// Get an iterator for the given attribute that initially points at the first vertex.
-		// Supported types T: CVector3D, CVector4D, float[2], SColor3ub, SColor4ub
+		// Get an iterator over the backing store for the given attribute that 
+		// initially points at the first vertex.
+		// Supported types T: CVector3D, CVector4D, float[2], SColor3ub, SColor4ub,
+		// u16, u16[2], u8, u8[4], short, short[2].
 		// This function verifies at runtime that the requested type T matches
 		// the attribute definition passed to AddAttribute().
 		template<typename T>
@@ -171,7 +173,8 @@ public:
 	// attributes.
 	// All vertex data is lost when a vertex array is re-layouted.
 	void Layout();
-	// (Re-)Upload the attributes of the vertex array.
+	// (Re-)Upload the attributes of the vertex array from the backing store to
+	// the underlying VBO object.
 	void Upload();
 	// Bind this array, returns the base address for calls to glVertexPointer etc.
 	u8* Bind();
@@ -204,13 +207,14 @@ private:
  * A VertexArray that is specialised to handle 16-bit array indices.
  * Call Bind() and pass the return value to the indices parameter of
  * glDrawElements/glDrawRangeElements/glMultiDrawElements.
- * Use CVertexBuffer::Unbind() to unbind the array.
+ * Use CVertexBuffer::Unbind() to unbind the array when done.
  */
 class VertexIndexArray : public VertexArray
 {
 public:
 	VertexIndexArray(GLenum usage);
 
+	/// Gets the iterator over the (only) attribute in this array, i.e. a u16.
 	VertexArrayIterator<u16> GetIterator() const;
 
 private:
diff --git a/source/renderer/VertexBuffer.cpp b/source/renderer/VertexBuffer.cpp
index bcbfebe..514d6eb 100644
--- a/source/renderer/VertexBuffer.cpp
+++ b/source/renderer/VertexBuffer.cpp
@@ -33,10 +33,11 @@ CVertexBuffer::CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target)
 {
 	size_t size = MAX_VB_SIZE_BYTES;
 
-	if (target == GL_ARRAY_BUFFER)
+	if (target == GL_ARRAY_BUFFER) // vertex data buffer
 	{
 		// We want to store 16-bit indices to any vertex in a buffer, so the
-		// buffer must never be bigger than vertexSize*64K bytes
+		// buffer must never be bigger than vertexSize*64K bytes since we can 
+		// address at most 64K of them with 16-bit indices
 		size = std::min(size, vertexSize*65536);
 	}
 
@@ -54,13 +55,13 @@ CVertexBuffer::CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target)
 	}
 
 	// store max/free vertex counts
-	m_MaxVertices=m_FreeVertices=size/vertexSize;
+	m_MaxVertices = m_FreeVertices = size/vertexSize;
 	
 	// create sole free chunk
-	VBChunk* chunk=new VBChunk;
-	chunk->m_Owner=this;
-	chunk->m_Count=m_FreeVertices;
-	chunk->m_Index=0;
+	VBChunk* chunk = new VBChunk;
+	chunk->m_Owner = this;
+	chunk->m_Count = m_FreeVertices;
+	chunk->m_Index = 0;
 	m_FreeList.push_front(chunk);
 }
 
@@ -100,11 +101,11 @@ CVertexBuffer::VBChunk* CVertexBuffer::Allocate(size_t vertexSize, size_t numVer
 		return 0;
 
 	// trawl free list looking for first free chunk with enough space
-	VBChunk* chunk=0;
+	VBChunk* chunk = 0;
 	typedef std::list<VBChunk*>::iterator Iter;
-	for (Iter iter=m_FreeList.begin();iter!=m_FreeList.end();++iter) {
-		if (numVertices<=(*iter)->m_Count) {
-			chunk=*iter;
+	for (Iter iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter) {
+		if (numVertices <= (*iter)->m_Count) {
+			chunk = *iter;
 			// remove this chunk from the free list
 			m_FreeList.erase(iter);
 			m_FreeVertices -= chunk->m_Count;
@@ -173,7 +174,7 @@ void CVertexBuffer::Release(VBChunk* chunk)
 
 ///////////////////////////////////////////////////////////////////////////////
 // UpdateChunkVertices: update vertex data for given chunk
-void CVertexBuffer::UpdateChunkVertices(VBChunk* chunk,void* data)
+void CVertexBuffer::UpdateChunkVertices(VBChunk* chunk, void* data)
 {
 	if (g_Renderer.m_Caps.m_VBO)
 	{
diff --git a/source/renderer/VertexBuffer.h b/source/renderer/VertexBuffer.h
index 816d83f..c426868 100644
--- a/source/renderer/VertexBuffer.h
+++ b/source/renderer/VertexBuffer.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -33,20 +33,22 @@
 // TODO: measure what influence this has on performance
 #define MAX_VB_SIZE_BYTES		(2*1024*1024)
 
-///////////////////////////////////////////////////////////////////////////////
-// CVertexBuffer: encapsulation of ARB_vertex_buffer_object, also supplying 
-// some additional functionality for sharing buffers between multiple objects
+/**
+ * CVertexBuffer: encapsulation of ARB_vertex_buffer_object, also supplying 
+ * some additional functionality for sharing buffers between multiple objects
+ */
 class CVertexBuffer
 {
 public:
-	// VBChunk: describes a portion of this vertex buffer
+
+	/// VBChunk: describes a portion of this vertex buffer
 	struct VBChunk
 	{
-		// owning buffer
+		/// Owning (parent) vertex buffer
 		CVertexBuffer* m_Owner;
-		// start index of this chunk in owner
+		/// Start index of this chunk in owner
 		size_t m_Index;
-		// number of vertices used by chunk
+		/// Number of vertices used by chunk
 		size_t m_Count;
 
 	private:
@@ -62,24 +64,24 @@ public:
 	CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target);
 	~CVertexBuffer();
 
-	// bind to this buffer; return pointer to address required as parameter
-	// to glVertexPointer ( + etc) calls
+	/// Bind to this buffer; return pointer to address required as parameter
+	/// to glVertexPointer ( + etc) calls
 	u8* Bind();
 
-	// get the address that Bind() will return, without actually binding
+	/// Get the address that Bind() will return, without actually binding
 	u8* GetBindAddress();
 
-	// unbind any currently-bound buffer, so glVertexPointer etc calls will not attempt to use it
+	/// Unbind any currently-bound buffer, so glVertexPointer etc calls will not attempt to use it
 	static void Unbind();
 
-	// update vertex data for given chunk
+	/// Update vertex data for given chunk. Transfers the provided data to the actual OpenGL vertex buffer.
 	void UpdateChunkVertices(VBChunk* chunk, void* data);
 
 	size_t GetVertexSize() const { return m_VertexSize; }
-
 	size_t GetBytesReserved() const;
 	size_t GetBytesAllocated() const;
 
+	/// Returns true if this vertex buffer is compatible with the specified vertex type and intended usage.
 	bool CompatibleVertexType(size_t vertexSize, GLenum usage, GLenum target);
 
 	void DumpStatus();
@@ -87,29 +89,29 @@ public:
 protected:
 	friend class CVertexBufferManager;		// allow allocate only via CVertexBufferManager
 	
-	// try to allocate a buffer of given number of vertices (each of given size), 
-	// and with the given type - return null if no free chunks available
+	/// Try to allocate a buffer of given number of vertices (each of given size), 
+	/// and with the given type - return null if no free chunks available
 	VBChunk* Allocate(size_t vertexSize, size_t numVertices, GLenum usage, GLenum target);
-	// return given chunk to this buffer
+	/// Return given chunk to this buffer
 	void Release(VBChunk* chunk);
 	
 	
 private:	
-	// vertex size of this vertex buffer
+	/// Vertex size of this vertex buffer
 	size_t m_VertexSize;
-	// number of vertices of above size in this buffer
+	/// Number of vertices of above size in this buffer
 	size_t m_MaxVertices;
-	// list of free chunks in this buffer
+	/// List of free chunks in this buffer
 	std::list<VBChunk*> m_FreeList;
-	// available free vertices - total of all free vertices in the free list
+	/// Available free vertices - total of all free vertices in the free list
 	size_t m_FreeVertices;
-	// handle to the actual GL vertex buffer object
+	/// Handle to the actual GL vertex buffer object
 	GLuint m_Handle;
-	// raw system memory for systems not supporting VBOs
+	/// Raw system memory for systems not supporting VBOs
 	u8* m_SysMem;
-	// usage type of the buffer (GL_STATIC_DRAW etc)
+	/// Usage type of the buffer (GL_STATIC_DRAW etc)
 	GLenum m_Usage;
-	// buffer target (GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER)
+	/// Buffer target (GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER)
 	GLenum m_Target;
 };
 
diff --git a/source/renderer/VertexBufferManager.h b/source/renderer/VertexBufferManager.h
index b97c68d..4ccb02f 100644
--- a/source/renderer/VertexBufferManager.h
+++ b/source/renderer/VertexBufferManager.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -30,8 +30,6 @@
 class CVertexBufferManager
 {
 public:
-	// Explicit shutdown of the vertex buffer subsystem
-	void Shutdown();
 	
 	/**
 	 * Try to allocate a vertex buffer of the given size and type.
@@ -44,17 +42,23 @@ public:
 	 */
 	CVertexBuffer::VBChunk* Allocate(size_t vertexSize, size_t numVertices, GLenum usage, GLenum target);
 
-	// return given chunk to its owner
+	/// Returns the given @p chunk to its owning buffer
 	void Release(CVertexBuffer::VBChunk* chunk);
 
-	// return list of all buffers
+	/// Returns a list of all buffers
 	const std::list<CVertexBuffer*>& GetBufferList() const { return m_Buffers; }
 
 	size_t GetBytesReserved();
 	size_t GetBytesAllocated();
 
+	/// Returns the maximum possible size of a single vertex buffer
+	size_t GetMaxBufferSize() const { return MAX_VB_SIZE_BYTES; }
+
+	/// Explicit shutdown of the vertex buffer subsystem; releases all currently-allocated buffers.
+	void Shutdown();
+
 private:
-	// list of all known vertex buffers
+	/// List of all known vertex buffers
 	std::list<CVertexBuffer*> m_Buffers;
 };
 
diff --git a/source/simulation2/components/CCmpRallyPointRenderer.cpp b/source/simulation2/components/CCmpRallyPointRenderer.cpp
index dc16628..0a52b6f 100644
--- a/source/simulation2/components/CCmpRallyPointRenderer.cpp
+++ b/source/simulation2/components/CCmpRallyPointRenderer.cpp
@@ -610,7 +610,7 @@ void CCmpRallyPointRenderer::ConstructOverlayLines()
 			// construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx
 			SOverlayTexturedLine overlayLine;
 			overlayLine.m_Thickness = m_LineThickness;
-			overlayLine.m_Terrain = cmpTerrain->GetCTerrain();
+			overlayLine.m_SimContext = &GetSimContext();
 			overlayLine.m_TextureBase = m_Texture;
 			overlayLine.m_TextureMask = m_TextureMask;
 			overlayLine.m_Color = m_LineColor;
@@ -634,14 +634,14 @@ void CCmpRallyPointRenderer::ConstructOverlayLines()
 		}
 		else
 		{
-			// construct dashed line from startPointIdx to endPointIdx, add textured overlay lines for it to the render list
+			// construct dashed line from startPointIdx to endPointIdx; add textured overlay lines for it to the render list
 			std::vector<CVector2D> straightLine;
 			straightLine.push_back(m_Path[segment.m_StartIndex]);
 			straightLine.push_back(m_Path[segment.m_EndIndex]);
 
-			// We always want to have the dashed line end at either point with a full dash (i.e. not a cleared space), so that the dashed
-			// area is visually obvious. That implies that we want at least So, let's do some calculations to see what size we should make 
-			// the dashes and clears.
+			// We always want to the dashed line to end at either point with a full dash (i.e. not a cleared space), so that the dashed
+			// area is visually obvious. This requires some calculations to see what size we should make the dashes and clears for them
+			// to fit exactly.
 
 			float maxDashSize = 3.f;
 			float maxClearSize = 3.f;
@@ -652,8 +652,8 @@ void CCmpRallyPointRenderer::ConstructOverlayLines()
 
 			float distance = (m_Path[segment.m_StartIndex] - m_Path[segment.m_EndIndex]).Length(); // straight-line distance between the points
 
-			// see how many pairs (dash + clear) can fit into the distance unmodified. Then check the remaining distance; if it's not exactly
-			// a dash size's worth (and it likely won't be), then adjust the dash/clear sizes slightly so that it is.
+			// See how many pairs (dash + clear) of unmodified size can fit into the distance. Then check the remaining distance; if it's not exactly
+			// a dash size's worth (which it probably won't be), then adjust the dash/clear sizes slightly so that it is.
 			int numFitUnmodified = floor(distance/(dashSize + clearSize));
 			float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize));
 
@@ -682,7 +682,7 @@ void CCmpRallyPointRenderer::ConstructOverlayLines()
 				SOverlayTexturedLine dashOverlay;
 
 				dashOverlay.m_Thickness = m_LineThickness;
-				dashOverlay.m_Terrain = cmpTerrain->GetCTerrain();
+				dashOverlay.m_SimContext = &GetSimContext();
 				dashOverlay.m_TextureBase = m_Texture;
 				dashOverlay.m_TextureMask = m_TextureMask;
 				dashOverlay.m_Color = m_LineDashColor;
@@ -767,7 +767,7 @@ void CCmpRallyPointRenderer::FixFootprintWaypoints(std::vector<CVector2D>& coord
 	{
 	case ICmpFootprint::SQUARE:
 		{
-			// in this case, footprintSize0 and 1 respectively indicate the (unrotated) size along the X and Z axes
+			// in this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
 
 			// the building's footprint could be rotated any which way, so let's get the rotation around the Y axis
 			// and the rotated unit vectors in the X/Z plane of the shape's footprint
@@ -837,7 +837,6 @@ void CCmpRallyPointRenderer::FixInvisibleWaypoints(std::vector<CVector2D>& coord
 	player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
 	CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
 
-	//for (std::vector<Waypoint>::iterator it = waypoints.begin(); it != waypoints.end();)
 	for(std::vector<CVector2D>::iterator it = coords.begin(); it != coords.end();)
 	{
 		int i = (fixed::FromFloat(it->X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
diff --git a/source/simulation2/components/CCmpSelectable.cpp b/source/simulation2/components/CCmpSelectable.cpp
index d23916f..b4b6460 100644
--- a/source/simulation2/components/CCmpSelectable.cpp
+++ b/source/simulation2/components/CCmpSelectable.cpp
@@ -20,17 +20,27 @@
 #include "simulation2/system/Component.h"
 #include "ICmpSelectable.h"
 
-#include "ICmpPosition.h"
-#include "ICmpFootprint.h"
-#include "ICmpVisual.h"
-#include "simulation2/MessageTypes.h"
-#include "simulation2/helpers/Render.h"
-
 #include "graphics/Overlay.h"
+#include "graphics/Terrain.h"
+#include "graphics/TextureManager.h"
+#include "maths/Ease.h"
 #include "maths/MathUtil.h"
 #include "maths/Matrix3D.h"
 #include "maths/Vector3D.h"
+#include "maths/Vector2D.h"
+#include "ps/CLogger.h"
 #include "renderer/Scene.h"
+#include "renderer/Renderer.h"
+#include "simulation2/MessageTypes.h"
+#include "simulation2/components/ICmpPosition.h"
+#include "simulation2/components/ICmpFootprint.h"
+#include "simulation2/components/ICmpVisual.h"
+#include "simulation2/components/ICmpTerrain.h"
+#include "simulation2/components/ICmpOwnership.h"
+#include "simulation2/components/ICmpPlayer.h"
+#include "simulation2/components/ICmpPlayerManager.h"
+#include "simulation2/components/ICmpWaterManager.h"
+#include "simulation2/helpers/Render.h"
 
 class CCmpSelectable : public ICmpSelectable
 {
@@ -39,27 +49,28 @@ public:
 	{
 		componentManager.SubscribeToMessageType(MT_Interpolate);
 		componentManager.SubscribeToMessageType(MT_RenderSubmit);
+		componentManager.SubscribeToMessageType(MT_OwnershipChanged);
+		componentManager.SubscribeToMessageType(MT_PositionChanged);
 		// TODO: it'd be nice if we didn't get these messages except in the rare
 		// cases where we're actually drawing a selection highlight
 	}
 
 	DEFAULT_COMPONENT_ALLOCATOR(Selectable)
 
-	SOverlayLine m_Overlay;
-	SOverlayLine* m_DebugBoundingBoxOverlay;
-	SOverlayLine* m_DebugSelectionBoxOverlay;
-	bool m_EditorOnly;
-
 	CCmpSelectable()
-		: m_DebugBoundingBoxOverlay(NULL), m_DebugSelectionBoxOverlay(NULL)
+		: m_DebugBoundingBoxOverlay(NULL), m_DebugSelectionBoxOverlay(NULL), 
+		  m_BuildingOverlay(NULL), m_UnitOverlay(NULL),
+		  m_FadeBaselineAlpha(0.f), m_FadeDeltaAlpha(0.f), m_FadeProgress(0.f)
 	{
-		m_Overlay.m_Thickness = 2;
-		m_Overlay.m_Color = CColor(0, 0, 0, 0);
+		m_Color = CColor(0, 0, 0, m_FadeBaselineAlpha);
 	}
 
-	~CCmpSelectable(){
+	~CCmpSelectable()
+	{
 		delete m_DebugBoundingBoxOverlay;
 		delete m_DebugSelectionBoxOverlay;
+		delete m_BuildingOverlay;
+		delete m_UnitOverlay;
 	}
 
 	static std::string GetSchema()
@@ -71,18 +82,52 @@ public:
 				"<element name='EditorOnly' a:help='If this element is present, the entity is only selectable in Atlas'>"
 					"<empty/>"
 				"</element>"
-			"</optional>";
+			"</optional>"
+			"<element name='Overlay' a:help='Specifies the type of overlay to be displayed when this entity is selected'>"
+				"<choice>"
+					"<element name='Texture' a:help='Displays a texture underneath the entity.'>"
+						"<element name='MainTexture' a:help='Texture to display underneath the entity. Filepath relative to art/textures/selection/.'><text/></element>"
+						"<element name='MainTextureMask' a:help='Mask texture that controls where to apply player color. Filepath relative to art/textures/selection/.'><text/></element>"
+					"</element>"
+					"<element name='Outline' a:help='Traces the outline of the entity with a line texture.'>"
+						"<element name='LineTexture' a:help='Texture to apply to the line. Filepath relative to art/textures/selection/.'><text/></element>"
+						"<element name='LineTextureMask' a:help='Texture that controls where to apply player color. Filepath relative to art/textures/selection/.'><text/></element>"
+						"<element name='LineThickness' a:help='Thickness of the line, in world units.'><ref name='positiveDecimal'/></element>"
+					"</element>"
+				"</choice>"
+			"</element>";
 	}
 
 	virtual void Init(const CParamNode& paramNode)
 	{
 		m_EditorOnly = paramNode.GetChild("EditorOnly").IsOk();
-	}
 
-	virtual void Deinit()
-	{
+		const CParamNode& textureNode = paramNode.GetChild("Overlay").GetChild("Texture");
+		const CParamNode& outlineNode = paramNode.GetChild("Overlay").GetChild("Outline");
+
+		const char* textureBasePath = "art/textures/selection/";
+
+		// Save some memory by using interned file paths in these descriptors (almost all actors and
+		// entities have this component, and many use the same textures).
+		if (textureNode.IsOk())
+		{
+			// textured quad mode (dynamic, for units)
+			m_OverlayDescriptor.m_Type = ICmpSelectable::DYNAMIC_QUAD;
+			m_OverlayDescriptor.m_QuadTexture = CStrIntern(textureBasePath + textureNode.GetChild("MainTexture").ToUTF8());
+			m_OverlayDescriptor.m_QuadTextureMask = CStrIntern(textureBasePath + textureNode.GetChild("MainTextureMask").ToUTF8());
+		}
+		else if (outlineNode.IsOk())
+		{
+			// textured outline mode (static, for buildings)
+			m_OverlayDescriptor.m_Type = ICmpSelectable::STATIC_OUTLINE;
+			m_OverlayDescriptor.m_LineTexture = CStrIntern(textureBasePath + outlineNode.GetChild("LineTexture").ToUTF8());
+			m_OverlayDescriptor.m_LineTextureMask = CStrIntern(textureBasePath + outlineNode.GetChild("LineTextureMask").ToUTF8());
+			m_OverlayDescriptor.m_LineThickness = outlineNode.GetChild("LineThickness").ToFloat();
+		}
 	}
 
+	virtual void Deinit() { }
+
 	virtual void Serialize(ISerializer& UNUSED(serialize))
 	{
 		// Nothing to do here (the overlay object is not worth saving, it'll get
@@ -95,88 +140,360 @@ public:
 		Init(paramNode);
 	}
 
-	virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
+	virtual void HandleMessage(const CMessage& msg, bool UNUSED(global));
+
+	virtual void SetSelectionHighlight(CColor color)
 	{
-		switch (msg.GetType())
-		{
-		case MT_Interpolate:
+		m_Color.r = color.r;
+		m_Color.g = color.g;
+		m_Color.b = color.b;
+
+		// set up fading from the current value (as the baseline) to the target value
+		m_FadeBaselineAlpha = m_Color.a;
+		m_FadeDeltaAlpha = color.a - m_FadeBaselineAlpha;
+		m_FadeProgress = 0.f;
+	}
+
+	virtual bool IsEditorOnly()
+	{
+		return m_EditorOnly;
+	}
+
+	void RenderSubmit(SceneCollector& collector);
+
+	/**
+	 * Called from RenderSubmit if using a static outline; responsible for ensuring that the static overlay 
+	 * is up-to-date before it is rendered. Has no effect unless the static overlay is explicitly marked as
+	 * invalid first (see InvalidateStaticOverlay).
+	 */
+	void UpdateStaticOverlay();
+
+	/**
+	 * Called from the interpolation handler; responsible for ensuring the dynamic overlay (provided we're
+	 * using one) is up-to-date and ready to be submitted to the next rendering run.
+	 */
+	void UpdateDynamicOverlay(float frameOffset);
+
+	/// Explicitly invalidates the static overlay.
+	void InvalidateStaticOverlay();
+
+private:
+	SOverlayDescriptor m_OverlayDescriptor;
+	SOverlayTexturedLine* m_BuildingOverlay;
+	SOverlayQuad* m_UnitOverlay;
+
+	SOverlayLine* m_DebugBoundingBoxOverlay;
+	SOverlayLine* m_DebugSelectionBoxOverlay;
+
+	bool m_EditorOnly;
+	
+	/// Current selection overlay color. Alpha component is subject to fading.
+	CColor m_Color;
+	/// Baseline alpha value to start fading from. Constant during a single fade.
+	float m_FadeBaselineAlpha;
+	/// Delta between target and baseline alpha. Constant during a single fade. Can be positive or negative.
+	float m_FadeDeltaAlpha;
+	/// Linear time progress of the fade, between 0 and m_FadeDuration.
+	float m_FadeProgress;
+	/// Total duration of a single fade, in seconds. Assumed constant for now; feel free to change this into
+	/// a member variable if you need to adjust it per component.
+	static const float FADE_DURATION;
+};
+
+const float CCmpSelectable::FADE_DURATION = 0.3f;
+
+void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global))
+{
+	switch (msg.GetType())
+	{
+	case MT_Interpolate:
 		{
-			if (m_Overlay.m_Color.a > 0)
+			const CMessageInterpolate& msgData = static_cast<const CMessageInterpolate&> (msg);
+
+			if (m_FadeDeltaAlpha != 0.f)
+			{
+				m_FadeProgress += msgData.frameTime;
+				if (m_FadeProgress >= FADE_DURATION)
+				{
+					const float targetAlpha = m_FadeBaselineAlpha + m_FadeDeltaAlpha;
+
+					// stop the fade
+					m_Color.a = targetAlpha;
+					m_FadeBaselineAlpha = targetAlpha;
+					m_FadeDeltaAlpha = 0.f;
+					m_FadeProgress = FADE_DURATION; // will need to be reset to start the next fade again
+				}
+				else
+				{
+					m_Color.a = Ease::QuartOut(m_FadeProgress, m_FadeBaselineAlpha, m_FadeDeltaAlpha, FADE_DURATION);
+				}
+			}
+
+			// update dynamic overlay only when visible
+			if (m_Color.a > 0)
 			{
-				float offset = static_cast<const CMessageInterpolate&> (msg).offset;
-				ConstructShape(offset);
+				UpdateDynamicOverlay(msgData.offset);
 			}
+
 			break;
 		}
-		case MT_RenderSubmit:
+	case MT_OwnershipChanged: 
 		{
-			if (m_Overlay.m_Color.a > 0)
-			{
-				const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
-				RenderSubmit(msgData.collector);
-			}
+			const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (msg);
+
+			// don't update color if there's no new owner (e.g. the unit died)
+			if (msgData.to == INVALID_PLAYER)
+				break;
+
+			// update the selection highlight color
+			CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY);
+			if (!cmpPlayerManager)
+				break;
+
+			CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(msgData.to));
+			if (!cmpPlayer)
+				break;
+
+			// Update the highlight color, while keeping the current alpha target value intact
+			// (i.e. baseline + delta), so that any ongoing fades simply continue with the new color.
+			CColor color = cmpPlayer->GetColour();
+			SetSelectionHighlight(CColor(color.r, color.g, color.b, m_FadeBaselineAlpha + m_FadeDeltaAlpha));
+		}
+		// fall-through
+	case MT_PositionChanged:
+		{
+			InvalidateStaticOverlay();
 			break;
 		}
+	case MT_RenderSubmit:
+		{
+			const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
+			RenderSubmit(msgData.collector);
+
+			break;
 		}
 	}
+}
 
-	virtual bool IsEditorOnly()
-	{
-		return m_EditorOnly;
-	}
+void CCmpSelectable::InvalidateStaticOverlay()
+{
+	SAFE_DELETE(m_BuildingOverlay);
+}
 
-	virtual void SetSelectionHighlight(CColor color)
+void CCmpSelectable::UpdateStaticOverlay()
+{
+	// Static overlays are allocated once and not updated until they are explicitly deleted again
+	// (see InvalidateStaticOverlay). Since they are expected to change rarely (if ever) during
+	// normal gameplay, this saves us doing all the work below on each frame.
+	
+	if (m_BuildingOverlay || m_OverlayDescriptor.m_Type != STATIC_OUTLINE)
+		return;
+	
+	if (!CRenderer::IsInitialised())
+		return;
+
+	entity_id_t entityId = GetEntityId();
+	CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), entityId);
+	CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), entityId);
+	if (!cmpFootprint || !cmpPosition || !cmpPosition->IsInWorld())
+		return;
+
+	CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
+	if (!cmpTerrain)
+		return; // should never happen
+
+	// grab position/footprint data
+	CFixedVector2D position = cmpPosition->GetPosition2D();
+	CFixedVector3D rotation = cmpPosition->GetRotation();
+
+	ICmpFootprint::EShape fpShape;
+	entity_pos_t fpSize0_fixed, fpSize1_fixed, fpHeight_fixed;
+	cmpFootprint->GetShape(fpShape, fpSize0_fixed, fpSize1_fixed, fpHeight_fixed);
+
+	CTextureProperties texturePropsBase(m_OverlayDescriptor.m_LineTexture.c_str()); 
+	texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); 
+	texturePropsBase.SetMaxAnisotropy(4.f);
+
+	CTextureProperties texturePropsMask(m_OverlayDescriptor.m_LineTextureMask.c_str());
+	texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); 
+	texturePropsMask.SetMaxAnisotropy(4.f);
+
+	// -------------------------------------------------------------------------------------
+
+	m_BuildingOverlay = new SOverlayTexturedLine;
+	m_BuildingOverlay->m_AlwaysVisible = false;
+	m_BuildingOverlay->m_Closed = true;
+	m_BuildingOverlay->m_SimContext = &GetSimContext();
+	m_BuildingOverlay->m_Thickness = m_OverlayDescriptor.m_LineThickness;
+	m_BuildingOverlay->m_TextureBase = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
+	m_BuildingOverlay->m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
+
+	CVector2D origin(position.X.ToFloat(), position.Y.ToFloat());
+
+	switch (fpShape)
 	{
-		m_Overlay.m_Color = color;
+	case ICmpFootprint::SQUARE:
+		{
+			float s = sinf(-rotation.Y.ToFloat());
+			float c = cosf(-rotation.Y.ToFloat());
+			CVector2D unitX(c, s);
+			CVector2D unitZ(-s, c);
+
+			// add half the line thickness to the radius so that we get an 'outside' stroke of the footprint shape
+			const float halfSizeX = fpSize0_fixed.ToFloat()/2.f + m_BuildingOverlay->m_Thickness/2.f;
+			const float halfSizeZ = fpSize1_fixed.ToFloat()/2.f + m_BuildingOverlay->m_Thickness/2.f;
 
-		if (color.a == 0 && !m_Overlay.m_Coords.empty())
+			std::vector<CVector2D> points;
+			points.push_back(CVector2D(origin + unitX *  halfSizeX    + unitZ *(-halfSizeZ)));
+			points.push_back(CVector2D(origin + unitX *(-halfSizeX)   + unitZ *(-halfSizeZ)));
+			points.push_back(CVector2D(origin + unitX *(-halfSizeX)   + unitZ *  halfSizeZ));
+			points.push_back(CVector2D(origin + unitX *  halfSizeX    + unitZ *  halfSizeZ));
+
+			SimRender::SubdividePoints(points, TERRAIN_TILE_SIZE/3.f, m_BuildingOverlay->m_Closed);
+			m_BuildingOverlay->PushCoords(points);
+		}
+		break;
+	case ICmpFootprint::CIRCLE:
 		{
-			// Delete the overlay data to save memory (we don't want hundreds of bytes
-			// times thousands of units when the selections are not being rendered any more)
-			std::vector<float> empty;
-			m_Overlay.m_Coords.swap(empty);
-			ENSURE(m_Overlay.m_Coords.capacity() == 0);
+			const float radius = fpSize0_fixed.ToFloat() + m_BuildingOverlay->m_Thickness/3.f;
+			if (radius > 0) // prevent catastrophic failure
+			{
+				float stepAngle;
+				unsigned numSteps;
+				SimRender::AngularStepFromChordLen(TERRAIN_TILE_SIZE/3.f, radius, stepAngle, numSteps);
+
+				for (unsigned i = 0; i < numSteps; i++) // '<' is sufficient because the line is closed automatically
+				{
+					float angle = i * stepAngle;
+					float px = origin.X + radius * sinf(angle);
+					float pz = origin.Y + radius * cosf(angle);
+
+					m_BuildingOverlay->PushCoords(px, pz);
+				}
+			}
 		}
+		break;
+	}
 
-		// TODO: it'd be nice to fade smoothly (but quickly) from transparent to solid
+	ENSURE(m_BuildingOverlay);
+}
+
+void CCmpSelectable::UpdateDynamicOverlay(float frameOffset)
+{
+	// Dynamic overlay lines are allocated once and never deleted. Since they are expected to change frequently,
+	// they are assumed dirty on every call to this function, and we should therefore use this function more
+	// thoughtfully than calling it right before every frame render.
+	
+	if (m_OverlayDescriptor.m_Type != DYNAMIC_QUAD)
+		return;
+
+	if (!CRenderer::IsInitialised())
+		return;
+
+	entity_id_t entityId = GetEntityId();
+	CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), entityId);
+	CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), entityId);
+	if (!cmpFootprint || !cmpPosition || !cmpPosition->IsInWorld())
+		return;
+
+	float rotY;
+	CVector2D position;
+	cmpPosition->GetInterpolatedPosition2D(frameOffset, position.X, position.Y, rotY);
+
+	CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
+	CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
+	ENSURE(cmpWaterManager && cmpTerrain);
+
+	CTerrain* terrain = cmpTerrain->GetCTerrain();
+	ENSURE(terrain);
+
+	ICmpFootprint::EShape fpShape;
+	entity_pos_t fpSize0_fixed, fpSize1_fixed, fpHeight_fixed;
+	cmpFootprint->GetShape(fpShape, fpSize0_fixed, fpSize1_fixed, fpHeight_fixed);
+
+	// ---------------------------------------------------------------------------------
+
+	if (!m_UnitOverlay)
+	{
+		m_UnitOverlay = new SOverlayQuad;
+
+		// Assuming we don't need the capability of swapping textures on-demand.
+		CTextureProperties texturePropsBase(m_OverlayDescriptor.m_QuadTexture.c_str()); 
+		texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); 
+		texturePropsBase.SetMaxAnisotropy(4.f);
+
+		CTextureProperties texturePropsMask(m_OverlayDescriptor.m_QuadTextureMask.c_str());
+		texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); 
+		texturePropsMask.SetMaxAnisotropy(4.f);
+
+		m_UnitOverlay->m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
+		m_UnitOverlay->m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
 	}
 
-	void ConstructShape(float frameOffset)
+	m_UnitOverlay->m_Color = m_Color;
+
+	// TODO: some code duplication here :< would be nice to factor out getting the corner points of an 
+	// entity based on its footprint sizes (and regardless of whether it's a circle or a square)
+
+	float s = sinf(-rotY);
+	float c = cosf(-rotY);
+	CVector2D unitX(c, s);
+	CVector2D unitZ(-s, c);
+
+	float halfSizeX = fpSize0_fixed.ToFloat();
+	float halfSizeZ = fpSize1_fixed.ToFloat();
+	if (fpShape == ICmpFootprint::SQUARE)
 	{
-		CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
-		if (!cmpPosition)
-			return;
+		halfSizeX /= 2.0f;
+		halfSizeZ /= 2.0f;
+	}
 
-		if (!cmpPosition->IsInWorld())
-			return;
+	std::vector<CVector2D> points;
+	points.push_back(CVector2D(position + unitX *(-halfSizeX)   + unitZ *  halfSizeZ));  // top left
+	points.push_back(CVector2D(position + unitX *(-halfSizeX)   + unitZ *(-halfSizeZ))); // bottom left
+	points.push_back(CVector2D(position + unitX *  halfSizeX    + unitZ *(-halfSizeZ))); // bottom right
+	points.push_back(CVector2D(position + unitX *  halfSizeX    + unitZ *  halfSizeZ));  // top right
 
-		float x, z, rotY;
-		cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, rotY);
+	for (int i=0; i < 4; i++)
+	{
+		float quadY = std::max(
+			terrain->GetExactGroundLevel(points[i].X, points[i].Y),
+			cmpWaterManager->GetExactWaterLevel(points[i].X, points[i].Y)
+		);
 
-		CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), GetEntityId());
-		if (!cmpFootprint)
-		{
-			// Default (this probably shouldn't happen) - just render an arbitrary-sized circle
-			SimRender::ConstructCircleOnGround(GetSimContext(), x, z, 2.f, m_Overlay, cmpPosition->IsFloating());
-		}
-		else
+		m_UnitOverlay->m_Corners[i] = CVector3D(points[i].X, quadY, points[i].Y);
+	}
+}
+
+void CCmpSelectable::RenderSubmit(SceneCollector& collector)
+{
+	// don't render selection overlay if it's not gonna be visible
+	if (m_Color.a > 0)
+	{
+		switch (m_OverlayDescriptor.m_Type)
 		{
-			ICmpFootprint::EShape shape;
-			entity_pos_t size0, size1, height;
-			cmpFootprint->GetShape(shape, size0, size1, height);
-
-			if (shape == ICmpFootprint::SQUARE)
-				SimRender::ConstructSquareOnGround(GetSimContext(), x, z, size0.ToFloat(), size1.ToFloat(), rotY, m_Overlay, cmpPosition->IsFloating());
-			else
-				SimRender::ConstructCircleOnGround(GetSimContext(), x, z, size0.ToFloat(), m_Overlay, cmpPosition->IsFloating());
+			case STATIC_OUTLINE:
+				{
+					UpdateStaticOverlay();
+					m_BuildingOverlay->m_Color = m_Color; // done separately so alpha changes don't require a full update call
+					collector.Submit(m_BuildingOverlay);
+				}
+				break;
+			case DYNAMIC_QUAD:
+				{
+					if (m_UnitOverlay)
+						collector.Submit(m_UnitOverlay);
+				}
+				break;
+			default:
+				break;
 		}
 	}
 
-	void RenderSubmit(SceneCollector& collector)
+	// Render bounding box debug overlays if we have a positive target alpha value. This ensures
+	// that the debug overlays respond immediately to deselection without delay from fading out.
+	if (m_FadeBaselineAlpha + m_FadeDeltaAlpha > 0)
 	{
-		// (This is only called if a > 0)
-		collector.Submit(&m_Overlay);
-
 		if (ICmpSelectable::ms_EnableDebugOverlays)
 		{
 			// allocate debug overlays on-demand
@@ -205,6 +522,6 @@ public:
 			if (m_DebugSelectionBoxOverlay) SAFE_DELETE(m_DebugSelectionBoxOverlay);
 		}
 	}
-};
+}
 
 REGISTER_COMPONENT_TYPE(Selectable)
diff --git a/source/simulation2/components/CCmpTemplateManager.cpp b/source/simulation2/components/CCmpTemplateManager.cpp
index 9061c5f..7b24af7 100644
--- a/source/simulation2/components/CCmpTemplateManager.cpp
+++ b/source/simulation2/components/CCmpTemplateManager.cpp
@@ -377,7 +377,14 @@ void CCmpTemplateManager::ConstructTemplateActor(const std::string& actorName, C
 
 	// Initialise the actor's name and make it an Atlas selectable entity.
 	std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(wstring_from_utf8(actorName)));
-	std::string xml = "<Entity><VisualActor><Actor>" + name + "</Actor></VisualActor><Selectable><EditorOnly/></Selectable></Entity>";
+	std::string xml = "<Entity>"
+	                      "<VisualActor><Actor>" + name + "</Actor></VisualActor>"
+	                      "<Selectable>"
+	                          "<EditorOnly/>"
+	                          "<Overlay><Texture><MainTexture>actor.png</MainTexture><MainTextureMask>actor_mask.png</MainTextureMask></Texture></Overlay>"
+	                      "</Selectable>"
+	                  "</Entity>";
+
 	CParamNode::LoadXMLString(out, xml.c_str());
 }
 
diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp
index 4aa01bf..03c4fff 100644
--- a/source/simulation2/components/CCmpTerritoryManager.cpp
+++ b/source/simulation2/components/CCmpTerritoryManager.cpp
@@ -620,11 +620,6 @@ void CCmpTerritoryManager::UpdateBoundaryLines()
 	texturePropsMask.SetMaxAnisotropy(2.f);
 	CTexturePtr textureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
 
-	CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
-	if (!cmpTerrain)
-		return;
-	CTerrain* terrain = cmpTerrain->GetCTerrain();
-
 	CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY);
 	if (!cmpPlayerManager)
 		return;
@@ -642,7 +637,7 @@ void CCmpTerritoryManager::UpdateBoundaryLines()
 		m_BoundaryLines.push_back(SBoundaryLine());
 		m_BoundaryLines.back().connected = boundaries[i].connected;
 		m_BoundaryLines.back().color = color;
-		m_BoundaryLines.back().overlay.m_Terrain = terrain;
+		m_BoundaryLines.back().overlay.m_SimContext = &GetSimContext();
 		m_BoundaryLines.back().overlay.m_TextureBase = textureBase;
 		m_BoundaryLines.back().overlay.m_TextureMask = textureMask;
 		m_BoundaryLines.back().overlay.m_Color = color;
@@ -650,7 +645,6 @@ void CCmpTerritoryManager::UpdateBoundaryLines()
 		m_BoundaryLines.back().overlay.m_Closed = true;
 
 		SimRender::SmoothPointsAverage(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed);
-
 		SimRender::InterpolatePointsRNS(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed, m_BorderSeparation);
 
 		std::vector<float>& points = m_BoundaryLines.back().overlay.m_Coords;
diff --git a/source/simulation2/components/ICmpSelectable.h b/source/simulation2/components/ICmpSelectable.h
index c7c8ec2..42f0026 100644
--- a/source/simulation2/components/ICmpSelectable.h
+++ b/source/simulation2/components/ICmpSelectable.h
@@ -18,6 +18,7 @@
 #ifndef INCLUDED_ICMPSELECTABLE
 #define INCLUDED_ICMPSELECTABLE
 
+#include "ps/CStrIntern.h"
 #include "simulation2/system/Interface.h"
 
 struct CColor;
@@ -25,6 +26,27 @@ struct CColor;
 class ICmpSelectable : public IComponent
 {
 public:
+
+	enum EOverlayType {
+		/// A single textured quad overlay, intended for entities that move around much, like units (e.g. foot soldiers, etc).
+		DYNAMIC_QUAD,
+		/// A more complex textured line overlay, composed of several textured line segments. Intended for entities that do not
+		/// move often, such as buildings (structures).
+		STATIC_OUTLINE,
+	};
+
+	struct SOverlayDescriptor
+	{
+		EOverlayType m_Type;
+		CStrIntern m_QuadTexture;
+		CStrIntern m_QuadTextureMask;
+		CStrIntern m_LineTexture;
+		CStrIntern m_LineTextureMask;
+		float m_LineThickness;
+		
+		SOverlayDescriptor() : m_LineThickness(0) { }
+	};
+
 	/**
 	 * Returns true if the entity is only selectable in Atlas editor, e.g. a decorative visual actor.
 	 */
diff --git a/source/simulation2/helpers/Geometry.cpp b/source/simulation2/helpers/Geometry.cpp
index b357561..5b713f0 100644
--- a/source/simulation2/helpers/Geometry.cpp
+++ b/source/simulation2/helpers/Geometry.cpp
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -45,6 +45,11 @@ CFixedVector2D Geometry::GetHalfBoundingBox(CFixedVector2D u, CFixedVector2D v,
 	);
 }
 
+float Geometry::ChordToCentralAngle(const float chordLength, const float radius)
+{
+	return acosf(1.f - SQR(chordLength)/(2.f*SQR(radius))); // cfr. law of cosines
+}
+
 fixed Geometry::DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize)
 {
 	/*
diff --git a/source/simulation2/helpers/Geometry.h b/source/simulation2/helpers/Geometry.h
index cb925b7..e8af00e 100644
--- a/source/simulation2/helpers/Geometry.h
+++ b/source/simulation2/helpers/Geometry.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2011 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -24,6 +24,7 @@
  */
 
 #include "maths/Fixed.h"
+#include "maths/MathUtil.h"
 
 class CFixedVector2D;
 
@@ -53,6 +54,14 @@ CFixedVector2D GetHalfBoundingBox(CFixedVector2D u, CFixedVector2D v, CFixedVect
 fixed DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
 
 /**
+ * Given a circle of radius @p radius, and a chord of length @p chordLength on this circle, computes the central angle formed by 
+ * connecting the chord's endpoints to the center of the circle.
+ * 
+ * @param radius Radius of the circle; must be strictly positive.
+ */
+float ChordToCentralAngle(const float chordLength, const float radius);
+
+/**
  * Find point closest to the given point on the edge of the given square or rectangle.
  *
  * @note Currently assumes the @p u and @p v vectors are perpendicular.
diff --git a/source/simulation2/helpers/Render.cpp b/source/simulation2/helpers/Render.cpp
index 450a28d..712a6d4 100644
--- a/source/simulation2/helpers/Render.cpp
+++ b/source/simulation2/helpers/Render.cpp
@@ -19,17 +19,18 @@
 
 #include "Render.h"
 
-#include "simulation2/Simulation2.h"
-#include "simulation2/components/ICmpTerrain.h"
-#include "simulation2/components/ICmpWaterManager.h"
 #include "graphics/Overlay.h"
 #include "graphics/Terrain.h"
 #include "maths/BoundingBoxAligned.h"
 #include "maths/BoundingBoxOriented.h"
 #include "maths/MathUtil.h"
+#include "maths/Quaternion.h"
 #include "maths/Vector2D.h"
 #include "ps/Profile.h"
-#include "maths/Quaternion.h"
+#include "simulation2/Simulation2.h"
+#include "simulation2/components/ICmpTerrain.h"
+#include "simulation2/components/ICmpWaterManager.h"
+#include "simulation2/helpers/Geometry.h"
 
 void SimRender::ConstructLineOnGround(const CSimContext& context, const std::vector<float>& xz,
 		SOverlayLine& overlay, bool floating, float heightOffset)
@@ -343,7 +344,7 @@ static CVector2D EvaluateSpline(float t, CVector2D a0, CVector2D a1, CVector2D a
 	return p + CVector2D(dp.Y*-offset, dp.X*offset);
 }
 
-void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples)
+void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples /* = 4 */)
 {
 	PROFILE("InterpolatePointsRNS");
 	ENSURE(segmentSamples > 0);
@@ -381,7 +382,6 @@ void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed
 
 	for (size_t i = 0; i < imax; ++i)
 	{
-
 		// Get the relevant points for this spline segment; each step interpolates the segment between p1 and p2; p0 and p3 are the points
 		// before p1 and after p2, respectively; they're needed to compute tangents and whatnot.
 		CVector2D p0; // normally points[(i-1+n)%n], but it's a bit more complicated due to open/closed paths -- see below
@@ -521,3 +521,50 @@ void SimRender::ConstructDashedLine(const std::vector<CVector2D>& keyPoints, SDa
 	}
 
 }
+
+void SimRender::AngularStepFromChordLen(const float maxChordLength, const float radius, float& out_stepAngle, unsigned& out_numSteps)
+{
+	float maxAngle = Geometry::ChordToCentralAngle(maxChordLength, radius);
+	out_numSteps = ceilf(float(2*M_PI)/maxAngle);
+	out_stepAngle = float(2*M_PI)/out_numSteps;
+}
+
+// TODO: this serves a similar purpose to SplitLine above, but is more general. Also, SplitLine seems to be implemented more
+// efficiently, might be nice to take some cues from it
+void SimRender::SubdividePoints(std::vector<CVector2D>& points, float maxSegmentLength, bool closed)
+{
+	size_t numControlPoints = points.size();
+	if (numControlPoints < 2)
+		return;
+
+	ENSURE(maxSegmentLength > 0);
+
+	size_t endIndex = numControlPoints;
+	if (!closed && numControlPoints > 2)
+		endIndex--;
+
+	std::vector<CVector2D> newPoints;
+
+	for (size_t i = 0; i < endIndex; i++)
+	{
+		const CVector2D& curPoint = points[i];
+		const CVector2D& nextPoint = points[(i+1) % numControlPoints];
+		const CVector2D line(nextPoint - curPoint);
+		CVector2D lineDirection = line.Normalized();
+
+		// include control point i + a list of intermediate points between i and i + 1 (excluding i+1 itself)
+		newPoints.push_back(curPoint);
+
+		// calculate how many intermediate points are needed so that each segment is of length <= maxSegmentLength
+		float lineLength = line.Length();
+		size_t numSegments = (size_t) ceilf(lineLength / maxSegmentLength);
+		float segmentLength = lineLength / numSegments;
+
+		for (size_t s = 1; s < numSegments; ++s) // start at one, we already included curPoint
+		{
+			newPoints.push_back(curPoint + lineDirection * (s * segmentLength));
+		}
+	}
+
+	points.swap(newPoints);
+}
\ No newline at end of file
diff --git a/source/simulation2/helpers/Render.h b/source/simulation2/helpers/Render.h
index 93e812d..6ed8be2 100644
--- a/source/simulation2/helpers/Render.h
+++ b/source/simulation2/helpers/Render.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2012 Wildfire Games.
  * This file is part of 0 A.D.
  *
  * 0 A.D. is free software: you can redistribute it and/or modify
@@ -76,6 +76,7 @@ void ConstructLineOnGround(const CSimContext& context, const std::vector<float>&
  * @param[in,out] overlay Updated overlay line representing this circle.
  * @param[in] floating If true, the circle conforms to water as well.
  * @param[in] heightOffset Height above terrain to offset the circle.
+ * @param heightOffset The vertical offset to apply to points, to raise the line off the terrain a bit.
  */
 void ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius,
 		SOverlayLine& overlay,
@@ -96,7 +97,7 @@ void ConstructSquareOnGround(const CSimContext& context, float x, float z, float
 		bool floating, float heightOffset = 0.25f);
 
 /**
- * Constructs a solid outline of an arbitrarily-aligned box.
+ * Constructs a solid outline of an arbitrarily-aligned bounding @p box.
  *
  * @param[in] box
  * @param[in,out] overlayLine Updated overlay line representing the oriented box.
@@ -104,12 +105,12 @@ void ConstructSquareOnGround(const CSimContext& context, float x, float z, float
 void ConstructBoxOutline(const CBoundingBoxOriented& box, SOverlayLine& overlayLine);
 
 /**
- * Constructs a solid outline of an axis-aligned bounding box.
+ * Constructs a solid outline of an axis-aligned bounding @p box.
  *
  * @param[in] bound
  * @param[in,out] overlayLine Updated overlay line representing the AABB.
  */
-void ConstructBoxOutline(const CBoundingBoxAligned& bound, SOverlayLine& overlayLine);
+void ConstructBoxOutline(const CBoundingBoxAligned& box, SOverlayLine& overlayLine);
 
 /**
  * Constructs a simple gimbal outline with the given radius and center.
@@ -124,7 +125,7 @@ void ConstructGimbal(const CVector3D& center, float radius, SOverlayLine& out, s
 
 /**
  * Constructs 3D axis marker overlay lines for the given coordinate system.
- * The overlay lines are colored RGB for the XYZ axes, respectively.
+ * The XYZ axes are colored RGB, respectively.
  *
  * @param[in] coordSystem Specifies the coordinate system.
  * @param[out] outX,outY,outZ Constructed overlay lines for each axes.
@@ -162,7 +163,33 @@ void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float off
  * @param[in] dashLength Length of a single dash. Must be strictly positive.
  * @param[in] blankLength Length of a single blank between dashes. Must be strictly positive.
  */
-void ConstructDashedLine(const std::vector<CVector2D>& linePoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength);
+void ConstructDashedLine(const std::vector<CVector2D>& linePoints, SDashedLine& dashedLineOut,
+		const float dashLength, const float blankLength);
+
+/**
+ * Computes angular step parameters @p out_stepAngle and @p out_numSteps, given a @p maxChordLength on a circle of radius @p radius.
+ * The resulting values satisfy @p out_numSteps * @p out_stepAngle = 2*PI.
+ * 
+ * This function is used to find the angular step parameters when drawing a circle outline approximated by several connected chords;
+ * it returns the step angle and number of steps such that the length of each resulting chord is less than or equal to @p maxChordLength.
+ * By stating that each chord cannot be longer than a particular length, a certain level of visual smoothness of the resulting circle
+ * outline can be guaranteed independently of the radius of the outline.
+ * 
+ * @param radius Radius of the circle. Must be strictly positive.
+ * @param maxChordLength Desired maximum length of individual chords. Must be strictly positive.
+ */
+void AngularStepFromChordLen(const float maxChordLength, const float radius, float& out_stepAngle, unsigned& out_numSteps);
+
+/**
+ * Subdivides a list of @p points into segments of maximum length @p maxSegmentLength that are of equal size between every two
+ * control points. The resulting subdivided list of points is written back to @p points.
+ * 
+ * @param points The list of intermediate points to subdivide.
+ * @param maxSegmentLength The maximum length of a single segment after subdivision. Must be strictly positive.
+ * @param closed Should the provided list of points be treated as a closed shape? If true, the resulting list of points will include
+ *        extra subdivided points between the last and the first point.
+ */
+void SubdividePoints(std::vector<CVector2D>& points, float maxSegmentLength, bool closed);
 
 } // namespace
 
diff --git a/source/simulation2/system/ParamNode.cpp b/source/simulation2/system/ParamNode.cpp
index 4226a61..7cbf1bd 100644
--- a/source/simulation2/system/ParamNode.cpp
+++ b/source/simulation2/system/ParamNode.cpp
@@ -205,6 +205,11 @@ const std::string CParamNode::ToUTF8() const
 	return utf8_from_wstring(m_Value);
 }
 
+const CStrIntern CParamNode::ToUTF8Intern() const
+{
+	return CStrIntern(utf8_from_wstring(m_Value));
+}
+
 int CParamNode::ToInt() const
 {
 	int ret = 0;
@@ -219,6 +224,15 @@ fixed CParamNode::ToFixed() const
 	return fixed::FromString(CStrW(m_Value));
 }
 
+float CParamNode::ToFloat() const 
+{
+	float ret = 0;
+	std::wstringstream strm;
+	strm << m_Value;
+	strm >> ret;
+	return ret;
+}
+
 bool CParamNode::ToBool() const
 {
 	if (m_Value == L"true")
diff --git a/source/simulation2/system/ParamNode.h b/source/simulation2/system/ParamNode.h
index 16e9b40..7caffbd 100644
--- a/source/simulation2/system/ParamNode.h
+++ b/source/simulation2/system/ParamNode.h
@@ -20,6 +20,7 @@
 
 #include "lib/file/vfs/vfs_path.h"
 #include "maths/Fixed.h"
+#include "ps/CStrIntern.h"
 #include "ps/Errors.h"
 #include "scriptinterface/ScriptVal.h"
 
@@ -169,6 +170,12 @@ public:
 	const std::string ToUTF8() const;
 
 	/**
+	 * Returns the content of this node as an internalized 8-bit string. Should only be used for
+	 * predictably small and frequently-used strings.
+	 */
+	const CStrIntern ToUTF8Intern() const;
+
+	/**
 	 * Parses the content of this node as an integer
 	 */
 	int ToInt() const;
@@ -179,6 +186,11 @@ public:
 	fixed ToFixed() const;
 
 	/**
+	 * Parses the content of this node as a floating-point number
+	 */
+	float ToFloat() const;
+
+	/**
 	 * Parses the content of this node as a boolean ("true" == true, anything else == false)
 	 */
 	bool ToBool() const;
diff --git a/source/simulation2/tests/test_CmpTemplateManager.h b/source/simulation2/tests/test_CmpTemplateManager.h
index d087929..0c3a0e5 100644
--- a/source/simulation2/tests/test_CmpTemplateManager.h
+++ b/source/simulation2/tests/test_CmpTemplateManager.h
@@ -78,7 +78,8 @@ public:
 		const CParamNode* actor = tempMan->LoadTemplate(ent2, "actor|example1", -1);
 		TS_ASSERT(actor != NULL);
 		TS_ASSERT_WSTR_EQUALS(actor->ToXML(),
-				L"<Selectable><EditorOnly></EditorOnly></Selectable><VisualActor><Actor>example1</Actor><SilhouetteDisplay>false</SilhouetteDisplay><SilhouetteOccluder>false</SilhouetteOccluder></VisualActor>");
+				L"<Selectable><EditorOnly></EditorOnly><Overlay><Texture><MainTexture>actor.png</MainTexture><MainTextureMask>actor_mask.png</MainTextureMask></Texture></Overlay></Selectable>"
+				L"<VisualActor><Actor>example1</Actor><SilhouetteDisplay>false</SilhouetteDisplay><SilhouetteOccluder>false</SilhouetteOccluder></VisualActor>");
 
 		const CParamNode* preview = tempMan->LoadTemplate(ent2, "preview|unit", -1);
 		TS_ASSERT(preview != NULL);
diff --git a/source/tools/selectiontexgen/selectiontexgen.py b/source/tools/selectiontexgen/selectiontexgen.py
new file mode 100644
index 0000000..cfd6611
--- /dev/null
+++ b/source/tools/selectiontexgen/selectiontexgen.py
@@ -0,0 +1,288 @@
+"""
+Generates basic square and circle selection overlay textures by parsing all the entity XML files and reading
+their Footprint components.
+
+For usage, invoke this script with --help.
+"""
+
+# This script uses PyCairo for plotting, since PIL (Python Imaging Library) is absolutely horrible. On Linux,
+# this should be merely a matter of installing a package (e.g. 'python-cairo' for Debian/Ubuntu), but on Windows
+# it's kind of tricky and requires some Google-fu. Fortunately, I have saved the working instructions below:
+# 
+# Grab a Win32 binary from http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/ and install PyCairo using 
+# the installer. The installer extracts the necessary files into Lib\site-packages\cairo within the folder where 
+# Python is installed. There are some extra DLLs which are required to make Cairo work, so we have to get these 
+# as well.
+# 
+# Head to http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/ and get the binary versions of Cairo 
+# (cairo_1.8.10-3_win32.zip at the time of writing), Fontconfig (fontconfig_2.8.0-2_win32.zip), Freetype 
+# (freetype_2.4.4-1_win32.zip), Expat (expat_2.0.1-1_win32.zip), libpng (libpng_1.4.3-1_win32.zip) and zlib 
+# (zlib_1.2.5-2_win32.zip). Version numbers may vary, so be adaptive! Each ZIP file will contain a bin subfolder 
+# with a DLL file in it. Put the following DLLs in Lib\site-packages\cairo within your Python installation:
+#
+#    freetype6.dll (from freetype_2.4.4-1_win32.zip)
+#    libcairo-2.dll (from cairo_1.8.10-3_win32.zip)
+#    libexpat-1.dll (from expat_2.0.1-1_win32.zip)
+#    libfontconfig-1.dll (from fontconfig_2.8.0-2_win32.zip)
+#    libpng14-14.dll (from libpng_1.4.3-1_win32.zip)
+#    zlib1.dll (from zlib_1.2.5-2_win32.zip).
+#
+# Should be all set now.
+
+import optparse
+import sys, os
+import math
+import operator
+import cairo					# Requires PyCairo (see notes above)
+from os.path import *
+from xml.dom import minidom
+
+def geqPow2(x):
+	"""Returns the smallest power of two that's equal to or greater than x"""
+	return int(2**math.ceil(math.log(x, 2)))
+
+def generateSelectionTexture(shape, textureW, textureH, outerStrokeW, innerStrokeW, outputDir):
+	
+	outputBasename = "%dx%d" % (textureW, textureH)
+	
+	# size of the image canvas containing the texture (may be larger to ensure power-of-two dimensions)
+	canvasW = geqPow2(textureW)
+	canvasH = geqPow2(textureH)
+	
+	# draw texture
+	texture = cairo.ImageSurface(cairo.FORMAT_ARGB32, canvasW, canvasH)
+	textureMask = cairo.ImageSurface(cairo.FORMAT_RGB24, canvasW, canvasH)
+	
+	ctxTexture = cairo.Context(texture)
+	ctxTextureMask = cairo.Context(textureMask)
+	
+	# fill entire image with transparent pixels
+	ctxTexture.set_source_rgba(1.0, 1.0, 1.0, 0.0)		# transparent
+	ctxTexture.rectangle(0, 0, textureW, textureH)			
+	ctxTexture.fill()									# fill current path
+	
+	ctxTextureMask.set_source_rgb(0.0, 0.0, 0.0)		# black
+	ctxTextureMask.rectangle(0, 0, canvasW, canvasH)	# (!)
+	ctxTextureMask.fill()
+	
+	pasteX = (canvasW - textureW)//2					# integer division, floored result
+	pasteY = (canvasH - textureH)//2					# integer division, floored result
+	ctxTexture.translate(pasteX, pasteY)				# translate all drawing so that the result is centered
+	ctxTextureMask.translate(pasteX, pasteY)
+	
+	# outer stroke width should always be >= inner stroke width, but let's play it safe
+	maxStrokeW = max(outerStrokeW, innerStrokeW)
+	
+	if shape == "square":
+		
+		rectW = textureW
+		rectH = textureH
+		
+		# draw texture (4px white outline, then overlay a 2px black outline)
+		ctxTexture.rectangle(maxStrokeW/2, maxStrokeW/2, rectW - maxStrokeW, rectH - maxStrokeW)
+		ctxTexture.set_line_width(outerStrokeW)
+		ctxTexture.set_source_rgba(1.0, 1.0, 1.0, 1.0)	# white
+		ctxTexture.stroke_preserve()					# stroke and maintain path
+		ctxTexture.set_line_width(innerStrokeW)
+		ctxTexture.set_source_rgba(0.0, 0.0, 0.0, 1.0)	# black
+		ctxTexture.stroke()								# stroke and clear path
+		
+		# draw mask (2px white)
+		ctxTextureMask.rectangle(maxStrokeW/2, maxStrokeW/2, rectW - maxStrokeW, rectH - maxStrokeW)
+		ctxTextureMask.set_line_width(innerStrokeW)
+		ctxTextureMask.set_source_rgb(1.0, 1.0, 1.0)
+		ctxTextureMask.stroke()
+		
+	elif shape == "circle":
+		
+		centerX = textureW//2
+		centerY = textureH//2
+		radius = textureW//2 - maxStrokeW/2				# allow for the strokes to fit 
+		
+		# draw texture
+		ctxTexture.arc(centerX, centerY, radius, 0, 2*math.pi)
+		ctxTexture.set_line_width(outerStrokeW)
+		ctxTexture.set_source_rgba(1.0, 1.0, 1.0, 1.0)	# white
+		ctxTexture.stroke_preserve()					# stroke and maintain path
+		ctxTexture.set_line_width(innerStrokeW)
+		ctxTexture.set_source_rgba(0.0, 0.0, 0.0, 1.0)	# black
+		ctxTexture.stroke()
+		
+		# draw mask
+		ctxTextureMask.arc(centerX, centerY, radius, 0, 2*math.pi)
+		ctxTextureMask.set_line_width(innerStrokeW)
+		ctxTextureMask.set_source_rgb(1.0, 1.0, 1.0)
+		ctxTextureMask.stroke()
+	
+	finalOutputDir = outputDir + "/" + shape
+	if not isdir(finalOutputDir):
+		os.makedirs(finalOutputDir)
+	
+	print "Generating " + os.path.normcase(finalOutputDir + "/" + outputBasename + ".png")
+	
+	texture.write_to_png(finalOutputDir + "/" + outputBasename + ".png")
+	textureMask.write_to_png(finalOutputDir + "/" + outputBasename + "_mask.png")
+
+
+def generateSelectionTextures(xmlTemplateDir, outputDir, outerStrokeScale, innerStrokeScale, snapSizes = False):
+	
+	# recursively list XML files
+	xmlFiles = []
+	
+	for dir, subdirs, basenames in os.walk(xmlTemplateDir):
+		for basename in basenames:
+			filename = join(dir, basename)
+			if filename[-4:] == ".xml":
+				xmlFiles.append(filename)
+	
+	textureTypesRaw = set()		# set of (type, w, h) tuples (so we can eliminate duplicates)
+	
+	# parse the XML files, and look for <Footprint> nodes that are a child of <Entity> and
+	# that do not have the disable attribute defined
+	for xmlFile in xmlFiles:
+		xmlDoc = minidom.parse(xmlFile)
+		rootNode = xmlDoc.childNodes[0]
+		
+		# we're only interested in entity templates
+		if not rootNode.nodeName == "Entity":
+			continue
+		
+		# check if this entity has a footprint definition
+		rootChildNodes = [n for n in rootNode.childNodes if n.localName is not None] # remove whitespace text nodes
+		footprintNodes = filter(lambda x: x.localName == "Footprint", rootChildNodes)
+		if not len(footprintNodes) == 1:
+			continue
+		
+		footprintNode = footprintNodes[0]
+		if footprintNode.hasAttribute("disable"):
+			continue
+		
+		# parse the footprint declaration
+		# Footprints can either have either one of these children:
+		# <Circle radius="xx.x" />
+		# <Square width="xx.x" depth="xx.x"/>
+		# There's also a <Height> node, but we don't care about it here.
+		
+		squareNodes = footprintNode.getElementsByTagName("Square")
+		circleNodes = footprintNode.getElementsByTagName("Circle")
+		
+		numSquareNodes = len(squareNodes)
+		numCircleNodes = len(circleNodes)
+		
+		if not (numSquareNodes + numCircleNodes == 1):
+			print "Invalid Footprint definition: insufficient or too many Square and/or Circle definitions in %s" % xmlFile
+		
+		texShape = None
+		texW = None		# in world-space units
+		texH = None		# in world-space units
+		
+		if numSquareNodes == 1:
+			texShape = "square"
+			texW = float(squareNodes[0].getAttribute("width"))
+			texH = float(squareNodes[0].getAttribute("depth"))
+		
+		elif numCircleNodes == 1:
+			texShape = "circle"
+			texW = float(circleNodes[0].getAttribute("radius"))
+			texH = texW
+		
+		textureTypesRaw.add((texShape, texW, texH))
+	
+	# endfor xmlFiles
+	
+	print "Found:     %d footprints (%d square, %d circle)" % (
+		len(textureTypesRaw),
+		len([x for x in textureTypesRaw if x[0] == "square"]),
+		len([x for x in textureTypesRaw if x[0] == "circle"])
+	)
+	
+	textureTypes = set()
+	
+	for type, w, h in textureTypesRaw:
+		if snapSizes:
+			# "snap" texture sizes to close-enough neighbours that will still look good enough so we can get away with fewer 
+			# actual textures than there are unique footprint outlines 
+			w = 1*math.ceil(w/1) 	# round up to the nearest world-space unit
+			h = 1*math.ceil(h/1)	# round up to the nearest world-space unit
+			
+		textureTypes.add((type, w, h))
+	
+	if snapSizes:
+		print "Reduced: %d footprints (%d square, %d circle)" % (
+			len(textureTypes),
+			len([x for x in textureTypes if x[0] == "square"]),
+			len([x for x in textureTypes if x[0] == "circle"])
+		)
+	
+	# create list from texture types set (so we can sort and have prettier output)
+	textureTypes = sorted(list(textureTypes), key=operator.itemgetter(0,1,2))		# sort by the first tuple element, then by the second, then the third
+	
+	# ------------------------------------------------------------------------------------
+	# compute the size of the actual texture we want to generate (in px)
+	
+	scale = 8		# world-space-units-to-pixels scale
+	for type, w, h in textureTypes:
+		
+		# if we have a circle, update the w and h so that they're the full width and height of the texture 
+		# and not just the radius
+		if type == "circle":
+			assert w == h
+			w *= 2
+			h *= 2
+		
+		w = int(math.ceil(w*scale))
+		h = int(math.ceil(h*scale))
+		
+		# apply a minimum size for really small textures
+		w = max(24, w)
+		h = max(24, h)
+		
+		generateSelectionTexture(type, w, h, w/outerStrokeScale, innerStrokeScale * (w/outerStrokeScale), outputDir)
+
+
+if __name__ == "__main__":
+	
+	parser = optparse.OptionParser(usage="Usage: %prog [filenames]")
+	
+	parser.add_option("--template-dir",        type="str",  default=None,  help="Path to simulation template XML definition folder. Will be searched recursively for templates containing Footprint definitions. If not specified and this script is run from its directory, it will be automatically determined.")
+	parser.add_option("--output-dir",          type="str",  default=".",   help="Output directory. Will be created if it does not already exist. Defaults to the current directory.")
+	parser.add_option("--oss", "--outer-stroke-scale",  type="float",  default=12.0, dest="outer_stroke_scale", metavar="SCALE",      help="Width of the outer (white) stroke, as a divisor of each generated texture's width. Defaults to 12. Larger values produce thinner overall outlines.")
+	parser.add_option("--iss", "--inner-stroke-scale",  type="float",  default=0.5,  dest="inner_stroke_scale", metavar="PERCENTAGE", help="Width of the inner (black) stroke, as a percentage of the outer stroke's calculated width. Must be between 0 and 1. Higher values produce thinner black/player color strokes inside the surrounding outer white stroke. Defaults to 0.5.")
+	
+	(options, args) = parser.parse_args()
+	
+	templateDir = options.template_dir
+	if templateDir is None:
+		
+		scriptDir = dirname(abspath(__file__))
+		
+		# 'autodetect' location if run from its own dir
+		if normcase(scriptDir).replace('\\', '/').endswith("source/tools/selectiontexgen"):
+			templateDir = "../../../binaries/data/mods/public/simulation/templates"
+		else:
+			print "No template dir specified; use the --template-dir command line argument."
+			sys.exit()
+	
+	# check if the template dir exists
+	templateDir = abspath(templateDir)
+	if not isdir(templateDir):
+		print "No such template directory: %s" % templateDir
+		sys.exit()
+	
+	# check if the output dir exists, create it if needed
+	outputDir = abspath(options.output_dir)
+	print outputDir
+	if not isdir(outputDir):
+		print "Creating output directory: %s" % outputDir
+		os.makedirs(outputDir)
+	
+	print "Template directory:\t%s" % templateDir
+	print "Output directory:  \t%s" % outputDir
+	print "------------------------------------------------"
+	
+	generateSelectionTextures(
+		templateDir,
+		outputDir,
+		max(0.0, options.outer_stroke_scale),
+		min(1.0, max(0.0, options.inner_stroke_scale)),
+	)
\ No newline at end of file
