diff --git a/binaries/data/mods/public/gui/session/session.xml b/binaries/data/mods/public/gui/session/session.xml
index bbf24cb..39e2b5c 100644
--- a/binaries/data/mods/public/gui/session/session.xml
+++ b/binaries/data/mods/public/gui/session/session.xml
@@ -89,7 +89,7 @@
 		/>
 
 		<!-- Dev/cheat commands -->
-		<object name="devCommands" size="100%-156 50%-80 100%-8 50%+80" type="image" sprite="devCommandsBackground"
+		<object name="devCommands" size="100%-156 50%-88 100%-8 50%+88" type="image" sprite="devCommandsBackground"
 				hidden="true" hotkey="session.devcommands.toggle">
 			<action on="Press">
 				toggleDeveloperOverlay();
@@ -126,23 +126,28 @@
 				<action on="Press">Engine.GuiInterfaceCall("SetRangeDebugOverlay", this.checked);</action>
 			</object>
 
-			<object size="0 96 100%-18 112" type="text" style="devCommandsText">Restrict camera</object>
-			<object size="100%-16 96 100% 112" type="checkbox" style="StoneCrossBox" checked="true">
+			<object size="0 96 100%-18 112" type="text" style="devCommandsText">Bounding box overlay</object>
+			<object size="100%-16 96 100% 112" type="checkbox" style="StoneCrossBox">
+				<action on="Press">Engine.SetBoundingBoxDebugOverlay(this.checked);</action>
+			</object>
+			
+			<object size="0 112 100%-18 128" type="text" style="devCommandsText">Restrict camera</object>
+			<object size="100%-16 112 100% 128" type="checkbox" style="StoneCrossBox" checked="true">
 				<action on="Press">gameView.constrainCamera = this.checked;</action>
 			</object>
 
-			<object size="0 112 100%-18 128" type="text" style="devCommandsText">Reveal map</object>
-			<object name="devCommandsRevealMap" size="100%-16 112 100% 128" type="checkbox" style="StoneCrossBox">
+			<object size="0 128 100%-18 144" type="text" style="devCommandsText">Reveal map</object>
+			<object size="100%-16 128 100% 144" type="checkbox" name="devCommandsRevealMap" style="StoneCrossBox">
 				<action on="Press">Engine.PostNetworkCommand({"type": "reveal-map", "enable": this.checked});</action>
 			</object>
 
-			<object size="0 128 100%-18 144" type="text" style="devCommandsText">Enable time warp</object>
-			<object size="100%-16 128 100% 144" type="checkbox" name="devTimeWarp" style="StoneCrossBox">
+			<object size="0 144 100%-18 160" type="text" style="devCommandsText">Enable time warp</object>
+			<object size="100%-16 144 100% 160" type="checkbox" name="devTimeWarp" style="StoneCrossBox">
 				<action on="Press">Engine.EnableTimeWarpRecording(this.checked ? 10 : 0);</action>
 			</object>
 
-			<object size="0 144 100%-18 160" type="text" style="devCommandsText">Promote selected units</object>
-			<object size="100%-16 144 100% 160" type="button" style="StoneCrossBox">
+			<object size="0 160 100%-18 176" type="text" style="devCommandsText">Promote selected units</object>
+			<object size="100%-16 160 100% 176" type="button" style="StoneCrossBox">
 				<action on="Press">Engine.PostNetworkCommand({"type": "promote", "entities": g_Selection.toList()});</action>
 			</object>
 		</object>
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 1ca7667..12874de 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
@@ -16,4 +16,7 @@
     <Type>wood.tree</Type>
   </ResourceSupply>
   <Selectable/>
+  <VisualActor>
+    <SelectionShape type="footprint" />
+  </VisualActor>
 </Entity>
diff --git a/source/graphics/Decal.cpp b/source/graphics/Decal.cpp
index 85b6ef9..06258eb 100644
--- a/source/graphics/Decal.cpp
+++ b/source/graphics/Decal.cpp
@@ -55,7 +55,7 @@ void CModelDecal::CalcBounds()
 {
 	ssize_t i0, j0, i1, j1;
 	CalcVertexExtents(i0, j0, i1, j1);
-	m_Bounds = m_Terrain->GetVertexesBound(i0, j0, i1, j1);
+	m_WorldBounds = m_Terrain->GetVertexesBound(i0, j0, i1, j1);
 }
 
 void CModelDecal::SetTerrainDirty(ssize_t i0, ssize_t j0, ssize_t i1, ssize_t j1)
diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp
index 7e17a4a..4e8205c 100644
--- a/source/graphics/GameView.cpp
+++ b/source/graphics/GameView.cpp
@@ -493,7 +493,7 @@ void CGameView::EnumerateObjects(const CFrustum& frustum, SceneCollector* c)
 			CPatch* patch=pTerrain->GetPatch(i,j);	// can't fail
 
 			// If the patch is underwater, calculate a bounding box that also contains the water plane
-			CBound bounds = patch->GetBounds();
+			CBound bounds = patch->GetWorldBounds();
 			float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight + 0.001f;
 			if(bounds[1].Y < waterHeight) {
 				bounds[1].Y = waterHeight;
diff --git a/source/graphics/Model.cpp b/source/graphics/Model.cpp
index 704e2b2..a6031c6 100644
--- a/source/graphics/Model.cpp
+++ b/source/graphics/Model.cpp
@@ -33,7 +33,6 @@
 #include "lib/res/graphics/ogl_tex.h"
 #include "lib/res/h_mgr.h"
 #include "ps/Profile.h"
-
 #include "ps/CLogger.h"
 
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -116,12 +115,12 @@ void CModel::CalcBounds()
 	if (! (m_Anim && m_Anim->m_AnimDef))
 	{
 		if (m_ObjectBounds.IsEmpty())
-			CalcObjectBounds();
+			CalcStaticObjectBounds();
 	}
 	else
 	{
 		if (m_Anim->m_ObjectBounds.IsEmpty())
-			CalcAnimatedObjectBound(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds);
+			CalcAnimatedObjectBounds(m_Anim->m_AnimDef, m_Anim->m_ObjectBounds);
 		ENSURE(! m_Anim->m_ObjectBounds.IsEmpty()); // (if this happens, it'll be recalculating the bounds every time)
 		m_ObjectBounds = m_Anim->m_ObjectBounds;
 	}
@@ -129,12 +128,13 @@ void CModel::CalcBounds()
 	// Ensure the transform is set correctly before we use it
 	ValidatePosition();
 
-	m_ObjectBounds.Transform(GetTransform(), m_Bounds);
+	// Now transform the object-space bounds to world-space bounds
+	m_ObjectBounds.Transform(GetTransform(), m_WorldBounds);
 }
 
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////
 // CalcObjectBounds: calculate object space bounds of this model, based solely on vertex positions
-void CModel::CalcObjectBounds()
+void CModel::CalcStaticObjectBounds()
 {
 	m_ObjectBounds.SetEmpty();
 
@@ -148,7 +148,7 @@ void CModel::CalcObjectBounds()
 
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////
 // CalcAnimatedObjectBound: calculate bounds encompassing all vertex positions for given animation 
-void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result)
+void CModel::CalcAnimatedObjectBounds(CSkeletonAnimDef* anim, CBound& result)
 {
 	result.SetEmpty();
 
@@ -199,14 +199,67 @@ void CModel::CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result)
 }
 
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////
-const CBound CModel::GetBoundsRec()
+const CBound CModel::GetWorldBoundsRec()
 {
-	CBound bounds = GetBounds();
+	CBound bounds = GetWorldBounds();
 	for (size_t i = 0; i < m_Props.size(); ++i)
-		bounds += m_Props[i].m_Model->GetBoundsRec();
+		bounds += m_Props[i].m_Model->GetWorldBoundsRec();
 	return bounds;
 }
 
+const CBound CModel::GetObjectSelectionBoundsRec()
+{
+	CBound objBounds = GetObjectBounds();		// updates the (children-not-included) object-space bounds if necessary
+
+	// now extend these bounds to include the props' selection bounds (if any)
+	for (size_t i = 0; i < m_Props.size(); ++i)
+	{
+		const Prop& prop = m_Props[i];
+		if (prop.m_Hidden)
+			continue; // prop is hidden from rendering, so it also shouldn't be used for selection
+
+		CBound propSelectionBounds = prop.m_Model->GetObjectSelectionBoundsRec();
+		if (propSelectionBounds.IsEmpty())
+			continue;	// submodel does not wish to participate in selection box, exclude it
+
+		// We have the prop's bounds in its own object-space; now we need to transform them into this coordinate space so 
+		// they can be properly added to our object-space. For that, we need the object-space transform of the prop 
+		// attachment point.
+		// 
+		// We have the prop point information; however, it's not trivial to compute its exact location in our object-space
+		// since it may or may not be attached to a bone (see SPropPoint), which in turn may or may not be in the middle of
+		// an animation. The bone matrices might be of interest, but they're really only meant to be used for the animation 
+		// system and are quite opaque to use from the outside (see @ref ValidatePosition).
+		// 
+		// However, a nice side effect of ValidatePosition is that it also computes the absolute world-space transform of 
+		// our props and sets it on their respective models. In particular, @ref ValidatePosition will compute the prop's
+		// world-space transform as either
+		// 
+		// T' = T x	B x O
+		// or 
+		// T' = T x O
+		// 
+		// where T' is the prop's world-space transform, T is our world-space transform, O is the prop's local
+		// offset/rotation matrix, and B is an optional transformation matrix of the bone the prop is attached to 
+		// (taking into account animation and everything).
+		// 
+		// From this, it is clear that either O or B x O is the object-space transformation matrix of the prop. So,
+		// all we need to do is apply our own inverse world-transform T^(-1) to T' to get our desired result. Luckily,
+		// this is precomputed upon setting the transform matrix (see @ref SetTransform), so it is free to fetch.
+		
+		CMatrix3D propObjectTransform = prop.m_Model->GetTransform(); // T'
+		propObjectTransform.Concatenate(GetInvTransform()); // T^(-1) x T'
+
+		// Transform the prop's bounds into our object coordinate space
+		CBound transformedPropSelectionBounds;
+		propSelectionBounds.Transform(propObjectTransform, transformedPropSelectionBounds);
+
+		objBounds += transformedPropSelectionBounds;
+	}
+
+	return objBounds;
+}
+
 /////////////////////////////////////////////////////////////////////////////////////////////////////////////
 // BuildAnimation: load raw animation frame animation from given file, and build a 
 // animation specific to this model
@@ -293,6 +346,7 @@ void CModel::ValidatePosition()
 	
 		m_Anim->m_AnimDef->BuildBoneMatrices(m_AnimTime, m_BoneMatrices, !(m_Flags & MODELFLAG_NOLOOPANIMATION));
 	
+		// add world-space transformation to m_BoneMatrices
 		const CMatrix3D& transform = GetTransform();
 		for (size_t i = 0; i < m_pModelDef->GetNumBones(); i++)
 			m_BoneMatrices[i].Concatenate(transform);
@@ -312,6 +366,8 @@ void CModel::ValidatePosition()
 		}
 	}
 	
+	// our own position is now valid; now we can safely update our props' positions without fearing 
+	// that doing so will cause a revalidation of this model (see recursion above).
 	m_PositionValid = true;
 	
 	// re-position and validate all props
@@ -321,9 +377,15 @@ void CModel::ValidatePosition()
 
 		CMatrix3D proptransform = prop.m_Point->m_Transform;;
 		if (prop.m_Point->m_BoneIndex != 0xff)
+		{
+			// m_BoneMatrices[i] already have world transform pre-applied (see above)
 			proptransform.Concatenate(m_BoneMatrices[prop.m_Point->m_BoneIndex]);
+		}
 		else
+		{
+			// not relative to any bone; just apply world-space transformation (i.e. relative to object-space origin)
 			proptransform.Concatenate(m_Transform);
+		}
 		
 		prop.m_Model->SetTransform(proptransform);
 		prop.m_Model->ValidatePosition();
@@ -409,6 +471,8 @@ void CModel::CopyAnimationFrom(CModel* source)
 void CModel::AddProp(const SPropPoint* point, CModelAbstract* model, CObjectEntry* objectentry)
 {
 	// position model according to prop point position
+
+	// this next call will invalidate the bounds of "model", which will in turn also invalidate the selection box
 	model->SetTransform(point->m_Transform);
 	model->m_Parent = this;
 
@@ -425,6 +489,10 @@ void CModel::AddAmmoProp(const SPropPoint* point, CModelAbstract* model, CObject
 	m_AmmoPropPoint = point;
 	m_AmmoLoadedProp = m_Props.size() - 1;
 	m_Props[m_AmmoLoadedProp].m_Hidden = true;
+
+	// we only need to invalidate the selection box here if it is based on props and their visibilities
+	if (!m_CustomSelectionShape)
+		m_SelectionBoxValid = false;
 }
 
 void CModel::ShowAmmoProp()
@@ -436,6 +504,10 @@ void CModel::ShowAmmoProp()
 	for (size_t i = 0; i < m_Props.size(); ++i)
 		if (m_Props[i].m_Point == m_AmmoPropPoint)
 			m_Props[i].m_Hidden = (i != m_AmmoLoadedProp);
+	
+	//  we only need to invalidate the selection box here if it is based on props and their visibilities
+	if (!m_CustomSelectionShape)
+		m_SelectionBoxValid = false;
 }
 
 void CModel::HideAmmoProp()
@@ -447,6 +519,10 @@ void CModel::HideAmmoProp()
 	for (size_t i = 0; i < m_Props.size(); ++i)
 		if (m_Props[i].m_Point == m_AmmoPropPoint)
 			m_Props[i].m_Hidden = (i == m_AmmoLoadedProp);
+
+	//  we only need to invalidate here if the selection box is based on props and their visibilities
+	if (!m_CustomSelectionShape)
+		m_SelectionBoxValid = false;
 }
 
 CModelAbstract* CModel::FindFirstAmmoProp()
diff --git a/source/graphics/Model.h b/source/graphics/Model.h
index 84efeb6..f75843a 100644
--- a/source/graphics/Model.h
+++ b/source/graphics/Model.h
@@ -54,11 +54,22 @@ public:
 	{
 		Prop() : m_Point(0), m_Model(0), m_ObjectEntry(0), m_Hidden(false) {}
 
+		/**
+		 * Location of the prop point within its parent model, relative to either a bone in the parent model or to the 
+		 * parent model's origin. See the documentation for @ref SPropPoint for more details.
+		 * @see SPropPoint
+		 */
 		const SPropPoint* m_Point;
+
+		/**
+		 * Pointer to the model associated with this prop. Note that the transform matrix held by this model is the full object-to-world
+		 * space transform, taking into account all parent model positioning (see @ref CModel::ValidatePosition for positioning logic).
+		 * @see CModel::ValidatePosition
+		 */
 		CModelAbstract* m_Model;
 		CObjectEntry* m_ObjectEntry;
 
-		bool m_Hidden; // temporarily removed from rendering
+		bool m_Hidden; ///< Should this prop be temporarily removed from rendering?
 	};
 
 public:
@@ -75,8 +86,6 @@ public:
 
 	// setup model from given geometry
 	bool InitModel(const CModelDefPtr& modeldef);
-	// calculate the world space bounds of this model
-	virtual void CalcBounds();
 	// update this model's state; 'time' is the absolute time since the start of the animation, in MS
 	void UpdateTo(float time);
 
@@ -133,12 +142,35 @@ public:
 			m_Props[i].m_Model->SetEntityVariable(name, value);
 	}
 
-	// calculate object space bounds of this model, based solely on vertex positions
-	void CalcObjectBounds();
-	// calculate bounds encompassing all vertex positions for given animation 
-	void CalcAnimatedObjectBound(CSkeletonAnimDef* anim,CBound& result);
+	// --- WORLD/OBJECT SPACE BOUNDS -----------------------------------------------------------------
+
+	/// Overridden to calculate both the world-space and object-space bounds of this model, and stores the result in
+	/// m_Bounds and m_ObjectBounds, respectively.
+	virtual void CalcBounds();
 
-	virtual const CBound GetBoundsRec();
+	/// Returns the object-space bounds for this model, excluding its children.
+	const CBound& GetObjectBounds()
+	{
+		RecalculateBoundsIfNecessary();				// recalculates both object-space and world-space bounds if necessary
+		return m_ObjectBounds;
+	}
+
+	virtual const CBound GetWorldBoundsRec();		// reimplemented here
+
+	/// Auxiliary method; calculates object space bounds of this model, based solely on vertex positions, and stores 
+	/// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcAnimatedObjectBounds) if it has been determined 
+	/// that the object-space bounds are static.
+	void CalcStaticObjectBounds();
+	
+	/// Auxiliary method; calculate object-space bounds encompassing all vertex positions for given animation, and stores 
+	/// the result in m_ObjectBounds. Called by CalcBounds (instead of CalcStaticBounds) if it has been determined that the 
+	/// object-space bounds need to take animations into account.
+	void CalcAnimatedObjectBounds(CSkeletonAnimDef* anim,CBound& result);
+
+	// --- SELECTION BOX/BOUNDS ----------------------------------------------------------------------
+
+	/// Reimplemented here since proper models should participate in selection boxes.
+	virtual const CBound GetObjectSelectionBoundsRec();
 
 	/**
 	 * Set transform of this object.
@@ -240,7 +272,15 @@ private:
 	CSkeletonAnim* m_Anim;
 	// time (in MS) into the current animation
 	float m_AnimTime;
-	// current state of all bones on this model; null if associated modeldef isn't skeletal
+	
+	/**
+	 * Current state of all bones on this model; null if associated modeldef isn't skeletal.
+	 * Props may attach to these bones by means of the SPropPoint::m_BoneIndex field; in this case their
+	 * transformation matrix held is relative to the bone transformation (see @ref SPropPoint and 
+	 * @ref CModel::ValidatePosition).
+	 * 
+	 * @see SPropPoint
+	 */
 	CMatrix3D* m_BoneMatrices;
 	// inverse matrices for the bind pose's bones; null if not skeletal
 	CMatrix3D* m_InverseBindBoneMatrices;
diff --git a/source/graphics/ModelAbstract.cpp b/source/graphics/ModelAbstract.cpp
new file mode 100644
index 0000000..8995029
--- /dev/null
+++ b/source/graphics/ModelAbstract.cpp
@@ -0,0 +1,91 @@
+/* Copyright (C) 2011 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/>.
+ */
+
+#include "precompiled.h"
+
+#include "ModelAbstract.h"
+
+#include "ps/CLogger.h"
+
+const CBox& CModelAbstract::GetSelectionBox()
+{
+	if (!m_SelectionBoxValid)
+	{
+		CalcSelectionBox();
+		m_SelectionBoxValid = true;
+	}
+	return m_SelectionBox;
+}
+
+void CModelAbstract::CalcSelectionBox()
+{
+	if (m_CustomSelectionShape)
+	{
+		// custom shape
+		switch(m_CustomSelectionShape->m_Type)
+		{
+		case CustomSelectionShape::BOX:
+			{
+				// create object-space bounds according to the information in the descriptor, and transform them to world-space.
+				// the box is centered on the X and Z axes, but extends from 0 to its height on the Y axis.
+				const float width = m_CustomSelectionShape->m_Size0;
+				const float depth = m_CustomSelectionShape->m_Size1;
+				const float height = m_CustomSelectionShape->m_Height;
+
+				CBound bounds;
+				bounds += CVector3D(-width/2.f, 0,     -depth/2.f);
+				bounds += CVector3D( width/2.f, height, depth/2.f);
+
+				bounds.Transform(GetTransform(), m_SelectionBox);
+			}
+			break;
+		case CustomSelectionShape::CYLINDER:
+			{
+				// TODO: unimplemented
+				m_SelectionBox.SetEmpty();
+				LOGWARNING(L"[ModelAbstract] TODO: Cylinder selection boxes are not yet implemented. Use BOX or BOUNDS instead.");
+			}
+		default:
+			{
+				m_SelectionBox.SetEmpty();
+				LOGWARNING(L"[ModelAbstract] Unrecognized selection shape type: %ld", m_CustomSelectionShape->m_Type);
+			}
+			break;
+		}
+	}
+	else
+	{
+		// standard method
+
+		// Get the object-space bounds that should be used to construct this model (and its children)'s selection box
+		CBound objBounds = GetObjectSelectionBoundsRec();
+		if (objBounds.IsEmpty())
+		{
+			m_SelectionBox.SetEmpty(); // model does not wish to participate in selection
+			return;
+		}
+
+		// Prevent the bounding box from extending through the terrain; clip the lower plane at Y=0 in object space.
+		if (objBounds[1].Y > 0.f) // should always be the case, unless the models are defined really weirdly
+			objBounds[0].Y = std::max(0.f, objBounds[0].Y);
+		//objBounds[0].Y = 0;
+
+		// transform object-space axis-aligned bounds to world-space arbitrary-aligned box
+		objBounds.Transform(GetTransform(), m_SelectionBox);
+	}
+	
+}
\ No newline at end of file
diff --git a/source/graphics/ModelAbstract.h b/source/graphics/ModelAbstract.h
index 2c01968..85ecb68 100644
--- a/source/graphics/ModelAbstract.h
+++ b/source/graphics/ModelAbstract.h
@@ -18,6 +18,7 @@
 #ifndef INCLUDED_MODELABSTRACT
 #define INCLUDED_MODELABSTRACT
 
+#include "maths/Box.h"
 #include "graphics/RenderableObject.h"
 #include "ps/Overlay.h"
 #include "simulation2/helpers/Player.h"
@@ -37,10 +38,39 @@ class CModelAbstract : public CRenderableObject
 	NONCOPYABLE(CModelAbstract);
 
 public:
-	CModelAbstract() :
-		m_Parent(NULL), m_PositionValid(false),
-		m_ShadingColor(1, 1, 1, 1), m_PlayerID(INVALID_PLAYER)
+
+	/**
+	 * Describes a custom selection shape to be used for a model's selection box instead of the default 
+	 * recursive bounding boxes.
+	 */
+	struct CustomSelectionShape
+	{
+		enum EType {
+			/// The selection shape is determined by an oriented box of custom, user-specified size.
+			BOX,
+			/// The selection shape is determined by a cylinder of custom, user-specified size.
+			CYLINDER
+		};
+
+		EType m_Type; ///< Type of shape. @see ESource
+		float m_Size0; ///< Box width if @ref BOX, or radius if @ref CYLINDER
+		float m_Size1; ///< Box depth if @ref BOX, or radius if @ref CYLINDER
+		float m_Height; ///< Box height if @ref BOX, cylinder height if @ref CYLINDER
+	};
+
+public:
+	
+	CModelAbstract()
+		: m_Parent(NULL), m_PositionValid(false), m_ShadingColor(1, 1, 1, 1), m_PlayerID(INVALID_PLAYER), 
+		  m_SelectionBoxValid(false), m_CustomSelectionShape(NULL)
 	{
+		m_SelectionBox.SetEmpty();
+	}
+
+	~CModelAbstract()
+	{
+		if (m_CustomSelectionShape) // allocated and set externally by CCmpVisualActor, but our responsibility to clean up
+			delete m_CustomSelectionShape;
 	}
 
 	virtual CModelAbstract* Clone() const = 0;
@@ -58,15 +88,47 @@ public:
 	// and this seems the easiest way to integrate with other code that wants
 	// type-specific processing)
 
+	/// Calls SetDirty on this model and all child objects.
+	virtual void SetDirtyRec(int dirtyflags) = 0;
+
+	/// Returns world space bounds of this object and all child objects.
+	virtual const CBound GetWorldBoundsRec() { return GetWorldBounds(); }
+
 	/**
-	 * Calls SetDirty on this model and all child objects.
+	 * Returns the world-space selection box of this model. Used primarily for hittesting against against a selection ray. The 
+	 * returned selection box may be empty to indicate that it does not wish to participate in the selection process.
 	 */
-	virtual void SetDirtyRec(int dirtyflags) = 0;
+	virtual const CBox& GetSelectionBox();
+
+	virtual void InvalidateBounds()
+	{
+		m_BoundsValid = false;
+		// a call to this method usually means that the model's transform has changed, i.e. it has moved or rotated, so we'll also
+		// want to update the selection box accordingly regardless of the shape it is built from.
+		m_SelectionBoxValid = false;
+	}
+
+	/// Sets a custom selection shape as described by a @p descriptor. Argument may be NULL 
+	/// if you wish to keep the default behaviour of using the recursively-calculated bounding boxes.
+	void SetCustomSelectionShape(CustomSelectionShape* descriptor)
+	{
+		if (m_CustomSelectionShape != descriptor)
+		{
+			m_CustomSelectionShape = descriptor;
+			m_SelectionBoxValid = false; // update the selection box when it is next requested
+		}
+	}
 
 	/**
-	 * Returns world space bounds of this object and all child objects.
+	 * Returns the (object-space) bounds that should be used to construct a selection box for this model and its children.
+	 * May return an empty bound to indicate that this model and its children should not be selectable themselves, or should
+	 * not be included in its parent model's selection box. This method is used for constructing the default selection boxes,
+	 * as opposed to any boxes of custom shape specified by @ref m_CustomSelectionShape.
+	 * 
+	 * If you wish your model type to be included in selection boxes, override this method and have it return the object-space
+	 * bounds of itself, augmented recursively (via this method) with the object-space selection bounds of its children.
 	 */
-	virtual const CBound GetBoundsRec() { return GetBounds(); }
+	virtual const CBound GetObjectSelectionBoundsRec() { return CBound::EMPTY; }
 
 	/**
 	 * Called when terrain has changed in the given inclusive bounds.
@@ -81,14 +143,12 @@ public:
 	virtual void SetEntityVariable(const std::string& UNUSED(name), float UNUSED(value)) { }
 
 	/**
-	 * Ensure that both the transformation and the bone
-	 * matrices are correct for this model and all its props.
+	 * Ensure that both the transformation and the bone matrices are correct for this model and all its props.
 	 */
 	virtual void ValidatePosition() = 0;
 
 	/**
-	 * Mark this model's position and bone matrices,
-	 * and all props' positions as invalid.
+	 * Mark this model's position and bone matrices, and all props' positions as invalid.
 	 */
 	virtual void InvalidatePosition() = 0;
 
@@ -100,7 +160,11 @@ public:
 	virtual void SetShadingColor(const CColor& colour) { m_ShadingColor = colour; }
 	virtual CColor GetShadingColor() const { return m_ShadingColor; }
 
-	/// If non-null points to the model that we are attached to.
+protected:
+	void CalcSelectionBox();
+
+public:
+	/// If non-null, points to the model that we are attached to.
 	CModelAbstract* m_Parent;
 
 	/// True if both transform and and bone matrices are valid.
@@ -108,8 +172,23 @@ public:
 
 	player_id_t m_PlayerID;
 
-	// modulating color
+	/// Modulating color
 	CColor m_ShadingColor;
+
+protected:
+
+	/// Selection box for this model.
+	CBox m_SelectionBox;
+
+	/// Is the current selection box valid?
+	bool m_SelectionBoxValid;
+
+	/// Pointer to a descriptor for a custom-defined selection box shape. If no custom selection box is required, this is NULL 
+	/// and the standard recursive-bounding-box-based selection box is used. Otherwise, a custom selection box described by this 
+	/// field will be used.
+	/// @see SetCustomSelectionShape
+	CustomSelectionShape* m_CustomSelectionShape;
+
 };
 
 #endif // INCLUDED_MODELABSTRACT
diff --git a/source/graphics/ModelDef.h b/source/graphics/ModelDef.h
index e7ce494..64f509b 100644
--- a/source/graphics/ModelDef.h
+++ b/source/graphics/ModelDef.h
@@ -32,19 +32,49 @@
 
 class CBoneState;
 
-///////////////////////////////////////////////////////////////////////////////
-// SPropPoint: structure describing a prop point
+/**
+ * Describes the position of a prop point within its parent model. A prop point is the location within a parent model
+ * where the prop's origin will be attached.
+ * 
+ * A prop point is specified by its transformation matrix (or separately by its position and rotation), which
+ * can be relative to either the parent model's origin, or one of the parent's bones. If the parent model is boned, 
+ * then the @ref m_BoneIndex field may specify a bone to which the transformation matrix is relative (see 
+ * @ref CModel::m_BoneMatrices). Otherwise, the transformation matrix is assumed to be relative to the parent model's 
+ * origin.
+ * 
+ * @see CModel::m_BoneMatrices
+ */
 struct SPropPoint
 {
-	// name of the prop point
+	/// Name of the prop point
 	CStr m_Name;
-	// position of the point
+
+	/**
+	 * Position of the point within the parent model, relative to either the parent model's origin or one of the parent 
+	 * model's bones if applicable. Also specified as part of @ref m_Transform.
+	 * @see m_Transform
+	 */
 	CVector3D m_Position;
-	// rotation of the point
+
+	/**
+	 * Rotation of the prop model that will be attached at this point. Also specified as part of @ref m_Transform.
+	 * @see m_Transform
+	 */
 	CQuaternion m_Rotation;
-	// object to parent space transformation 
+
+	/**
+	 * Object to parent space transformation. Combines both @ref m_Position and @ref m_Rotation in a single
+	 * transformation matrix. This transformation is relative to either the parent model's origin, or one of its
+	 * bones, depending on whether it is skeletal. If relative to a bone, then the bone in the parent model to
+	 * which this transformation is relative may be found by m_BoneIndex.
+	 * @see m_Position, m_Rotation
+	 */
 	CMatrix3D m_Transform;
-	// index of parent bone; 0xff if unboned
+
+	/**
+	 * Index of parent bone to which this prop point is relative, if any. The value 0xFF specifies that either the parent
+	 * model is unboned, or that this prop point is relative to the parent model's origin rather than one if its bones.
+	 */
 	u8 m_BoneIndex;
 };
 
@@ -233,3 +263,4 @@ private:
 };
 
 #endif
+ 
\ No newline at end of file
diff --git a/source/graphics/ObjectEntry.cpp b/source/graphics/ObjectEntry.cpp
index 25054ab..069ea31 100644
--- a/source/graphics/ObjectEntry.cpp
+++ b/source/graphics/ObjectEntry.cpp
@@ -129,7 +129,7 @@ bool CObjectEntry::BuildVariation(const std::vector<std::set<CStr> >& selections
 	model->SetTexture(texture);
 
 	// calculate initial object space bounds, based on vertex positions
-	model->CalcObjectBounds();
+	model->CalcStaticObjectBounds();
 
 	// load the animations
 	for (std::multimap<CStr, CObjectBase::Anim>::iterator it = variation.anims.begin(); it != variation.anims.end(); ++it)
diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h
index e07b13b..da47d19 100644
--- a/source/graphics/Overlay.h
+++ b/source/graphics/Overlay.h
@@ -36,6 +36,11 @@ 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
+
+	/// 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); }
 };
 
 /**
diff --git a/source/graphics/ParticleEmitter.cpp b/source/graphics/ParticleEmitter.cpp
index ab18c17..866b97b 100644
--- a/source/graphics/ParticleEmitter.cpp
+++ b/source/graphics/ParticleEmitter.cpp
@@ -232,7 +232,7 @@ void CModelParticleEmitter::CalcBounds()
 	// current computed particle positions plus the emitter type's largest
 	// potential bounding box at the current position
 
-	m_Bounds = m_Type->CalculateBounds(m_Emitter->GetPosition(), m_Emitter->GetParticleBounds());
+	m_WorldBounds = m_Type->CalculateBounds(m_Emitter->GetPosition(), m_Emitter->GetParticleBounds());
 }
 
 void CModelParticleEmitter::ValidatePosition()
diff --git a/source/graphics/Patch.cpp b/source/graphics/Patch.cpp
index 38fbf90..e8a1e9c 100644
--- a/source/graphics/Patch.cpp
+++ b/source/graphics/Patch.cpp
@@ -57,7 +57,7 @@ void CPatch::Initialize(CTerrain* parent,ssize_t x,ssize_t z)
 // CalcBounds: calculating the bounds of this patch
 void CPatch::CalcBounds()
 {
-	m_Bounds.SetEmpty();
+	m_WorldBounds.SetEmpty();
 
 	for (ssize_t j=0;j<PATCH_SIZE+1;j++)
 	{
@@ -65,14 +65,14 @@ void CPatch::CalcBounds()
 		{
 			CVector3D pos;
 			m_Parent->CalcPosition(m_X*PATCH_SIZE+i,m_Z*PATCH_SIZE+j,pos);
-			m_Bounds+=pos;
+			m_WorldBounds+=pos;
 		}
 	}
 
 	// If this a side patch, the sides go down to height 0, so add them
 	// into the bounds
 	if (GetSideFlags())
-		m_Bounds[0].Y = std::min(m_Bounds[0].Y, 0.f);
+		m_WorldBounds[0].Y = std::min(m_WorldBounds[0].Y, 0.f);
 }
 
 int CPatch::GetSideFlags()
diff --git a/source/graphics/RenderableObject.h b/source/graphics/RenderableObject.h
index 1b642f8..338eed5 100644
--- a/source/graphics/RenderableObject.h
+++ b/source/graphics/RenderableObject.h
@@ -86,20 +86,29 @@ public:
 		if (m_RenderData) m_RenderData->m_UpdateFlags|=dirtyflags;
 	}
 
-	// calculate (and store in m_Bounds) the world space bounds of this object
-	// - must be implemented by all concrete subclasses
+	/**
+	 * (Re)calculates and stores any bounds or bound-dependent data for this object. At this level, this is only the world-space bounds stored 
+	 * in @ref m_WorldBounds; subclasses may use this method to (re)compute additional bounds if necessary, or any data that depends on the
+	 * bounds. Whenever bound-dependent data is requested through a public interface, @ref RecalculateBoundsIfNecessary should be called 
+	 * first to ensure bound correctness, which will in turn call this method if it turns out that they're outdated.
+	 * 
+	 * @see m_BoundsValid
+	 * @see RecalculateBoundsIfNecessary
+	 */
 	virtual void CalcBounds() = 0;
 
-	// return world space bounds of this object
-	const CBound& GetBounds() {
-		if (! m_BoundsValid) {
-			CalcBounds();
-			m_BoundsValid = true;
-		}
-		return m_Bounds;
+	/// Returns the world-space axis-aligned bounds of this object.
+	const CBound& GetWorldBounds() {
+		RecalculateBoundsIfNecessary();
+		return m_WorldBounds;
 	}
 
-	void InvalidateBounds() { m_BoundsValid = false; }
+	/**
+	 * Marks the bounds as invalid. This will trigger @ref RecalculateBoundsIfNecessary to recompute any bound-related data the next time
+	 * any bound-related data is requested through a public interface -- at least, if you've made sure to call it before returning the
+	 * stored data.
+	 */
+	virtual void InvalidateBounds() { m_BoundsValid = false; }
 
 	// Set the object renderdata and free previous renderdata, if any.
 	void SetRenderData(CRenderData* renderdata) {
@@ -107,13 +116,23 @@ public:
 		m_RenderData = renderdata;
 	}
 
-	// return object renderdata - can be null if renderer hasn't yet
-	// created the renderdata
+	/// Return object renderdata - can be null if renderer hasn't yet created the renderdata
 	CRenderData* GetRenderData() { return m_RenderData; }
 
 protected:
-	// object bounds
-	CBound m_Bounds;
+	/// Factored out so subclasses don't need to repeat this if they want to add additional getters for bounds-related methods
+	/// (since they'll have to make sure to recalc the bounds if necessary before they return it).
+	void RecalculateBoundsIfNecessary()
+	{
+		if (!m_BoundsValid) {
+			CalcBounds();
+			m_BoundsValid = true;
+		}
+	}
+
+protected:
+	/// World-space bounds of this object
+	CBound m_WorldBounds;
 	// local->world space transform
 	CMatrix3D m_Transform;
 	// world->local space transform
@@ -121,8 +140,17 @@ protected:
 	// object renderdata
 	CRenderData* m_RenderData;
 
-private:
-	// remembers whether m_bounds needs to be recalculated
+	/**
+	 * Remembers whether any bounds need to be recalculated. Subclasses that add any data that depends on the bounds should 
+	 * take care to consider the validity of the bounds and recalculate their data when necessary -- overriding @ref CalcBounds
+	 * to do so would be a good idea, since it's already set up to be called by @ref RecalculateBoundsIfNecessary whenever the
+	 * bounds are marked as invalid. The latter should then be called before returning any bounds or bounds-derived data through
+	 * a public interface (see the implementation of @ref GetWorldBounds for an example).
+	 * 
+	 * @see CalcBounds
+	 * @see InvalidateBounds
+	 * @see RecalculateBoundsIfNecessary
+	 */
 	bool m_BoundsValid;
 };
 
diff --git a/source/graphics/UnitManager.cpp b/source/graphics/UnitManager.cpp
index d8604fe..0e3d7d1 100644
--- a/source/graphics/UnitManager.cpp
+++ b/source/graphics/UnitManager.cpp
@@ -100,11 +100,11 @@ CUnit* CUnitManager::PickUnit(const CVector3D& origin, const CVector3D& dir) con
 		CUnit* unit = m_Units[i];
 		float tmin, tmax;
 		
-		if (unit->GetModel().GetBounds().RayIntersect(origin, dir, tmin, tmax))
+		if (unit->GetModel().GetWorldBounds().RayIntersect(origin, dir, tmin, tmax))
 		{
 			// Point of closest approach
 			CVector3D obj;
-			unit->GetModel().GetBounds().GetCentre(obj);
+			unit->GetModel().GetWorldBounds().GetCentre(obj);
 			CVector3D delta = obj - origin;
 			float distance = delta.Dot(dir);
 			CVector3D closest = origin + dir * distance;
diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
index 0054246..7df679a 100644
--- a/source/gui/scripting/ScriptFunctions.cpp
+++ b/source/gui/scripting/ScriptFunctions.cpp
@@ -48,6 +48,7 @@
 #include "simulation2/components/ICmpGuiInterface.h"
 #include "simulation2/components/ICmpRangeManager.h"
 #include "simulation2/components/ICmpTemplateManager.h"
+#include "simulation2/components/ICmpSelectable.h"
 #include "simulation2/helpers/Selection.h"
 
 #include "js/jsapi.h"
@@ -456,6 +457,11 @@ void RewindTimeWarp(void* UNUSED(cbdata))
 	g_Game->GetTurnManager()->RewindTimeWarp();
 }
 
+void SetBoundingBoxDebugOverlay(void* UNUSED(cbdata), bool enabled)
+{
+	ICmpSelectable::ms_EnableDebugOverlays = enabled;
+}
+
 } // namespace
 
 void GuiScriptingInit(ScriptInterface& scriptInterface)
@@ -520,4 +526,5 @@ void GuiScriptingInit(ScriptInterface& scriptInterface)
 	scriptInterface.RegisterFunction<void, &DumpSimState>("DumpSimState");
 	scriptInterface.RegisterFunction<void, unsigned int, &EnableTimeWarpRecording>("EnableTimeWarpRecording");
 	scriptInterface.RegisterFunction<void, &RewindTimeWarp>("RewindTimeWarp");
+	scriptInterface.RegisterFunction<void, bool, &SetBoundingBoxDebugOverlay>("SetBoundingBoxDebugOverlay");
 }
diff --git a/source/maths/Bound.cpp b/source/maths/Bound.cpp
index bf6d798..a9b8886 100644
--- a/source/maths/Bound.cpp
+++ b/source/maths/Bound.cpp
@@ -28,9 +28,11 @@
 #include <float.h>
 
 #include "graphics/Frustum.h"
+#include "maths/Box.h"
 #include "maths/Brush.h"
 #include "maths/Matrix3D.h"
 
+const CBound CBound::EMPTY = CBound(); // initializes to an empty bound
 
 ///////////////////////////////////////////////////////////////////////////////
 // RayIntersect: intersect ray with this bound; return true
@@ -136,7 +138,7 @@ bool CBound::IsEmpty() const
 // Transform: transform this bound by given matrix; return transformed bound
 // in 'result' parameter - slightly modified version of code in Graphic Gems
 // (can't remember which one it was, though)
-void CBound::Transform(const CMatrix3D& m,CBound& result) const
+void CBound::Transform(const CMatrix3D& m, CBound& result) const
 {
 	ENSURE(this!=&result);
 
@@ -161,6 +163,54 @@ void CBound::Transform(const CMatrix3D& m,CBound& result) const
 	}
 }
 
+void CBound::Transform(const CMatrix3D& transform, CBox& result) const
+{
+	// The idea is this: compute the corners of this bounding box, transform them according to the specified matrix,
+	// then derive the box center, orientation vectors, and half-sizes.
+	const CVector3D& pMin = m_Data[0];
+	const CVector3D& pMax = m_Data[1];
+
+	// Find the corners of these bounds. We only need some of the corners to derive the information we need, so let's 
+	// not actually compute all of them. The corners are numbered starting from the minimum position (m_Data[0]), going
+	// counter-clockwise in the bottom plane, and then in the same order for the top plane (starting from the corner
+	// that's directly above the minimum position). Hence, corner0 is pMin and corner6 is pMax, so we don't need to
+	// custom-create those.
+	
+	CVector3D corner0; // corner0 is pMin, no need to copy it
+	CVector3D corner1(pMax.X, pMin.Y, pMin.Z);
+	CVector3D corner3(pMin.X, pMin.Y, pMax.Z);
+	CVector3D corner4(pMin.X, pMax.Y, pMin.Z);
+	CVector3D corner6; // corner6 is pMax, no need to copy it
+
+	// transform corners to world space
+	corner0 = transform.Transform(pMin); // = corner0
+	corner1 = transform.Transform(corner1);
+	corner3 = transform.Transform(corner3);
+	corner4 = transform.Transform(corner4);
+	corner6 = transform.Transform(pMax); // = corner6
+
+	// Compute orientation vectors, half-size vector, and box center. We can get the orientation vectors by just taking
+	// the directional vectors from a specific corner point (corner0) to the other corners, once in each direction. The
+	// half-sizes are similarly computed by taking the distances of those sides and dividing them by 2. Finally, the 
+	// center is simply the middle between the transformed pMin and pMax corners.
+
+	const CVector3D sideU(corner1 - corner0);
+	const CVector3D sideV(corner4 - corner0);
+	const CVector3D sideW(corner3 - corner0);
+
+	result.m_Basis[0] = sideU.Normalized();
+	result.m_Basis[1] = sideV.Normalized();
+	result.m_Basis[2] = sideW.Normalized();
+
+	result.m_HalfSizes = CVector3D(
+		sideU.Length()/2.f,
+		sideV.Length()/2.f,
+		sideW.Length()/2.f
+	);
+
+	result.m_Center = (corner0 + corner6) * 0.5f;
+}
+
 
 ///////////////////////////////////////////////////////////////////////////////
 // Intersect with the given frustum in a conservative manner
diff --git a/source/maths/Bound.h b/source/maths/Bound.h
index 7a22eee..9a106b2 100644
--- a/source/maths/Bound.h
+++ b/source/maths/Bound.h
@@ -27,18 +27,32 @@
 
 class CFrustum;
 class CMatrix3D;
+class CBox;
 
 ///////////////////////////////////////////////////////////////////////////////
 // CBound: basic axis aligned bounding box class
 class CBound
 {
 public:
+	
 	CBound() { SetEmpty(); }
-	CBound(const CVector3D& min,const CVector3D& max) {
-		m_Data[0]=min; m_Data[1]=max;
+	CBound(const CVector3D& min, const CVector3D& max) {
+		m_Data[0] = min;
+		m_Data[1] = max;
 	}
 
-	void Transform(const CMatrix3D& m,CBound& result) const;
+	/**
+	 * Transforms these bounds according to the specified transformation matrix @m, and writes the axis-aligned bounds
+	 * of that result to @p result.
+	 */
+	void Transform(const CMatrix3D& m, CBound& result) const;
+
+	/**
+	 * Transform these bounds using the matrix @p transform, and write out the result as an arbitrarily-aligned box.
+	 * The difference with @ref Transform(const CMatrix3D&, CBound&) is that that method is equivalent to first computing 
+	 * this result, and then taking the axis-aligned bounding boxes again.
+	 */
+	void Transform(const CMatrix3D& m, CBox& result) const;
 
 	CVector3D& operator[](int index) {	return m_Data[index]; }
 	const CVector3D& operator[](int index) const { return m_Data[index]; }
@@ -70,17 +84,26 @@ public:
 		return *this;
 	}
 
-	bool RayIntersect(const CVector3D& origin,const CVector3D& dir,float& tmin,float& tmax) const;
+	/**
+	 * Returns true if the ray originating in @p origin and with unit direction vector @p dir intersects this AABB, false otherwise.
+	 * Additionally, returns the positive distances from the origin of the ray to the entry and exit points in the bounding box in 
+	 * @p tmin and @p tmax. See also Real-Time Rendering, Third Edition by T. Akenine-Moller, p. 741--742.
+	 * @param origin Origin of the ray.
+	 * @param dir Direction vector of the ray. Must be of unit length.
+	 */
+	bool RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tmin, float& tmax) const;
 
 	// return the volume of this bounding box
-	float GetVolume() const {
-		CVector3D v=m_Data[1]-m_Data[0];
-		return std::max(v.X, 0.0f)*std::max(v.Y, 0.0f)*std::max(v.Z, 0.0f);
+	float GetVolume() const
+	{
+		CVector3D v = m_Data[1] - m_Data[0];
+		return (std::max(v.X, 0.0f) * std::max(v.Y, 0.0f) * std::max(v.Z, 0.0f));
 	}
 
 	// return the centre of this bounding box
-	void GetCentre(CVector3D& centre) const {
-		centre=(m_Data[0]+m_Data[1])*0.5f;
+	void GetCentre(CVector3D& centre) const
+	{
+		centre = (m_Data[0] + m_Data[1]) * 0.5f;
 	}
 
 	/**
@@ -109,7 +132,12 @@ public:
 	void Render() const;
 
 private:
+	// Holds the minimal and maximal coordinate points in m_Data[0] and m_Data[1], respectively.
 	CVector3D m_Data[2];
+
+public:
+	static const CBound EMPTY;
+
 };
 //////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
diff --git a/source/maths/Box.cpp b/source/maths/Box.cpp
new file mode 100644
index 0000000..799ba48
--- /dev/null
+++ b/source/maths/Box.cpp
@@ -0,0 +1,86 @@
+/* Copyright (C) 2011 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/>.
+ */
+
+#include "precompiled.h"
+
+#include "Box.h"
+#include "maths/Bound.h"
+
+#include <float.h>
+
+const CBox CBox::EMPTY = CBox();
+
+CBox::CBox(const CBound& bound)
+{
+	if (bound.IsEmpty())
+	{
+		SetEmpty();
+	}
+	else
+	{
+		bound.GetCentre(m_Center);
+
+		// the axes of an AABB are the world-space axes
+		m_Basis[0].X = 1.f; m_Basis[0].Y = 0.f; m_Basis[0].Z = 0.f;
+		m_Basis[1].X = 0.f; m_Basis[1].Y = 1.f; m_Basis[1].Z = 0.f;
+		m_Basis[2].X = 0.f; m_Basis[2].Y = 0.f; m_Basis[2].Z = 1.f;
+
+		// element-wise division by two to get half sizes (remember, [1] and [0] are the max and min coord points)
+		m_HalfSizes = (bound[1] - bound[0]) * 0.5f;
+	}
+}
+
+bool CBox::RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tMin_out, float& tMax_out) const
+{
+	// See Real-Time Rendering, Third Edition, p. 743
+	float tMin = -FLT_MAX;
+	float tMax = FLT_MAX;
+
+	CVector3D p = m_Center - origin;
+
+	for (int i = 0; i < 3; ++i)
+	{
+		float e = m_Basis[i].Dot(p);
+		float f = m_Basis[i].Dot(dir);
+
+		if(fabs(f) > 1e-10f)
+		{
+			float invF = 1.f/f;
+			float t1 = (e + m_HalfSizes[i]) * invF;
+			float t2 = (e - m_HalfSizes[i]) * invF;
+
+			if (t1 > t2)
+			{
+				float tmp = t1;
+				t1 = t2;
+				t2 = tmp;
+			}
+			if (t1 > tMin) tMin = t1;
+			if (t2 < tMax) tMax = t2;
+			if (tMin > tMax) return false;
+			if (tMax < 0) return false;
+		}
+		else
+		{
+			if(-e - m_HalfSizes[i] > 0 || -e + m_HalfSizes[i] < 0) return false;
+		}
+	}
+
+	tMin_out = tMin;
+	tMax_out = tMax;
+	return true;
+}
\ No newline at end of file
diff --git a/source/maths/Box.h b/source/maths/Box.h
new file mode 100644
index 0000000..6caebbf
--- /dev/null
+++ b/source/maths/Box.h
@@ -0,0 +1,101 @@
+/* Copyright (C) 2011 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_BOX
+#define INCLUDED_BOX
+
+#include "maths/Vector3D.h"
+
+class CBound;
+
+/*
+ * Generic oriented box. Originally intended to be used an Oriented Bounding Box (OBB), as opposed to CBound which is always aligned
+ * to the world-space axes (hence axis-aligned bounding box, or AABB).
+ */
+class CBox 
+{
+public:
+	
+	/// Empty constructor; creates an empty box
+	CBox() { SetEmpty(); }
+
+	/**
+	 * Constructs a new oriented box centered at @p centered and with normalized side vectors @p u, @p v and @p w. These vectors should 
+	 * be mutually orthonormal for a proper rectangular box. The half-widths of the box in each direction are given by @p hU, @p hV 
+	 * and @p hW, respectively.
+	 */
+	CBox(const CVector3D& center, const CVector3D& u, const CVector3D& v, const CVector3D& w, const CVector3D& halfSizes)
+		: m_Center(center), m_HalfSizes(halfSizes)
+	{
+		m_Basis[0] = u;
+		m_Basis[1] = v;
+		m_Basis[2] = w;
+	}
+
+	/// Constructs a new box from an axis-aligned bounding box (AABB).
+	explicit CBox(const CBound& bound);
+
+	/**
+	 * Returns true if the ray originating in @p origin and with unit direction vector @p dir intersects this box, false otherwise.
+	 * Additionally, returns the positive distances from the origin of the ray to the entry and exit points in the box in 
+	 * @p tmin and @p tmax. See also Real-Time Rendering, Third Edition by T. Akenine-Möller, p. 741--742.
+	 * Should not be used if IsEmpty() is true.
+	 *
+	 * @param origin Origin of the ray.
+	 * @param dir Direction vector of the ray. Must be of unit length.
+	 */
+	bool RayIntersect(const CVector3D& origin, const CVector3D& dir, float& tMin, float& tMax) const;
+
+	/**
+	 * Returns the corner at coordinate (@p u, @p v, @p w). Each of @p u, @p v and @p w must be exactly 1 or -1.
+	 * Should not be used if IsEmpty() is true.
+	 */
+	void GetCorner(int u, int v, int w, CVector3D& out) const
+	{
+		out = m_Center + m_Basis[0]*(u*m_HalfSizes[0]) + m_Basis[1]*(v*m_HalfSizes[1]) + m_Basis[2]*(w*m_HalfSizes[2]);
+	}
+
+	void SetEmpty()
+	{
+		// everything is zero
+		m_Center = CVector3D();
+		m_Basis[0] = CVector3D();
+		m_Basis[1] = CVector3D();
+		m_Basis[2] = CVector3D();
+		m_HalfSizes = CVector3D();
+	}
+
+	bool IsEmpty() const
+	{
+		return (   m_Center.X == 0 &&    m_Center.Y == 0 &&    m_Center.Z == 0 &&
+		         m_Basis[0].X == 0 &&  m_Basis[0].Y == 0 &&  m_Basis[0].Z == 0 && 
+		         m_Basis[1].X == 0 &&  m_Basis[1].Y == 0 &&  m_Basis[1].Z == 0 && 
+		         m_Basis[2].X == 0 &&  m_Basis[2].Y == 0 &&  m_Basis[2].Z == 0 &&
+		        m_HalfSizes.X == 0 && m_HalfSizes.Y == 0 && m_HalfSizes.Z == 0);
+	}
+
+public:
+	CVector3D m_Center; ///< Centroid location of the box
+	CVector3D m_HalfSizes; ///< Half the sizes of the box in each dimension (u,v,w). Positive values are expected.
+	/// Basis vectors (u,v,w) of the sides. Must always be normalized, and should be 
+	/// orthogonal for a proper rectangular cuboid.
+	CVector3D m_Basis[3];
+
+	static const CBox EMPTY;
+};
+
+#endif INCLUDED_BOX
\ No newline at end of file
diff --git a/source/maths/Matrix3D.h b/source/maths/Matrix3D.h
index 0a14bd3..84968db 100644
--- a/source/maths/Matrix3D.h
+++ b/source/maths/Matrix3D.h
@@ -35,6 +35,7 @@ class CMatrix3D
 public:
 	// the matrix data itself - accessible as either longhand names
 	// or via a flat or 2d array
+	// NOTE: _xy means row x, column y, so don't be fooled by the way they're listed below
 	union {
 		struct {
 			float _11, _21, _31, _41;
@@ -155,6 +156,12 @@ public:
 				 _14 == m._14 && _24 == m._24 && _34 == m._34 && _44 == m._44;
 	}
 
+	// inequality
+	bool operator!=(const CMatrix3D& m) const
+	{
+		return !(*this == m);
+	}
+
 	// set this matrix to the identity matrix
 	void SetIdentity();
 	// set this matrix to the zero matrix
diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp
index b8308bc..731f53d 100644
--- a/source/renderer/Renderer.cpp
+++ b/source/renderer/Renderer.cpp
@@ -1041,7 +1041,7 @@ public:
 
 	bool Filter(CModel *model)
 	{
-		return m_Frustum.IsBoxVisible(CVector3D(0, 0, 0), model->GetBoundsRec());
+		return m_Frustum.IsBoxVisible(CVector3D(0, 0, 0), model->GetWorldBoundsRec());
 	}
 
 private:
@@ -1753,10 +1753,10 @@ void CRenderer::SubmitNonRecursive(CModel* model)
 {
 	if (model->GetFlags() & MODELFLAG_CASTSHADOWS) {
 //		PROFILE( "updating shadow bounds" );
-		m->shadow->AddShadowedBound(model->GetBounds());
+		m->shadow->AddShadowedBound(model->GetWorldBounds());
 	}
 
-	// Tricky: The call to GetBounds() above can invalidate the position
+	// Tricky: The call to GetWorldBounds() above can invalidate the position
 	model->ValidatePosition();
 
 	bool canUseInstancing = false;
diff --git a/source/renderer/TerrainRenderer.cpp b/source/renderer/TerrainRenderer.cpp
index 13b71bc..56b1eca 100644
--- a/source/renderer/TerrainRenderer.cpp
+++ b/source/renderer/TerrainRenderer.cpp
@@ -173,14 +173,14 @@ bool TerrainRenderer::CullPatches(const CFrustum* frustum)
 	m->filteredPatches.clear();
 	for (std::vector<CPatchRData*>::iterator it = m->visiblePatches.begin(); it != m->visiblePatches.end(); it++)
 	{
-		if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetPatch()->GetBounds()))
+		if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetPatch()->GetWorldBounds()))
 			m->filteredPatches.push_back(*it);
 	}
 
 	m->filteredDecals.clear();
 	for (std::vector<CDecalRData*>::iterator it = m->visibleDecals.begin(); it != m->visibleDecals.end(); it++)
 	{
-		if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetDecal()->GetBounds()))
+		if (frustum->IsBoxVisible(CVector3D(0, 0, 0), (*it)->GetDecal()->GetWorldBounds()))
 			m->filteredDecals.push_back(*it);
 	}
 
diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h
index a7ac94b..b2c3042 100644
--- a/source/simulation2/TypeList.h
+++ b/source/simulation2/TypeList.h
@@ -71,6 +71,10 @@ COMPONENT(CommandQueue)
 INTERFACE(Decay)
 COMPONENT(Decay)
 
+// Note: The VisualActor component relies on this component being initialized before itself, in order to support using
+// an entity's footprint shape for the selection boxes. This dependency is not strictly necessary, but it does avoid
+// some extra plumbing code to set up on-demand initialization. If you find yourself forced to break this dependency, 
+// see VisualActor's Init method for a description of how you can avoid it.
 INTERFACE(Footprint)
 COMPONENT(Footprint)
 
@@ -142,6 +146,8 @@ COMPONENT(UnitMotionScripted)
 INTERFACE(Vision)
 COMPONENT(Vision)
 
+// Note: this component relies on the Footprint component being initialized before itself. See the comments above for
+// the Footprint component to find out why.
 INTERFACE(Visual)
 COMPONENT(VisualActor)
 
diff --git a/source/simulation2/components/CCmpProjectileManager.cpp b/source/simulation2/components/CCmpProjectileManager.cpp
index e0ac1cc..c7ffccd 100644
--- a/source/simulation2/components/CCmpProjectileManager.cpp
+++ b/source/simulation2/components/CCmpProjectileManager.cpp
@@ -347,7 +347,7 @@ void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrust
 
 		model.ValidatePosition();
 
-		if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetBounds()))
+		if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetWorldBounds()))
 			continue;
 
 		// TODO: do something about LOS (copy from CCmpVisualActor)
diff --git a/source/simulation2/components/CCmpSelectable.cpp b/source/simulation2/components/CCmpSelectable.cpp
index e9aa3d2..d5865e0 100644
--- a/source/simulation2/components/CCmpSelectable.cpp
+++ b/source/simulation2/components/CCmpSelectable.cpp
@@ -22,6 +22,7 @@
 
 #include "ICmpPosition.h"
 #include "ICmpFootprint.h"
+#include "ICmpVisual.h"
 #include "simulation2/MessageTypes.h"
 #include "simulation2/helpers/Render.h"
 
@@ -153,8 +154,28 @@ public:
 	void RenderSubmit(SceneCollector& collector)
 	{
 		// (This is only called if a > 0)
-
 		collector.Submit(&m_Overlay);
+
+		if (ICmpSelectable::ms_EnableDebugOverlays)
+		{
+			static SOverlayLine boundOverlayLine;
+			static SOverlayLine selectionOverlayLine;
+
+			CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), GetEntityId()); 
+			if (!cmpVisual.null()) 
+			{
+				SimRender::ConstructBoxOutline(cmpVisual->GetBounds(), boundOverlayLine);
+				boundOverlayLine.m_Thickness = 2; 
+				boundOverlayLine.m_Color = CColor(1.f, 0.f, 0.f, 1.f);
+
+				SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), selectionOverlayLine);
+				selectionOverlayLine.m_Thickness = 2;
+				selectionOverlayLine.m_Color = CColor(0.f, 1.f, 0.f, 1.f);
+
+				collector.Submit(&boundOverlayLine);
+				collector.Submit(&selectionOverlayLine);
+			}
+		}
 	}
 };
 
diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp
index f35cb80..6965af2 100644
--- a/source/simulation2/components/CCmpVisualActor.cpp
+++ b/source/simulation2/components/CCmpVisualActor.cpp
@@ -24,6 +24,7 @@
 #include "ICmpRangeManager.h"
 #include "ICmpVision.h"
 #include "simulation2/MessageTypes.h"
+#include "simulation2/components/ICmpFootprint.h"
 
 #include "graphics/Frustum.h"
 #include "graphics/Model.h"
@@ -97,7 +98,44 @@ public:
 			"</element>"
 			"<element name='SilhouetteOccluder'>"
 				"<data type='boolean'/>"
-			"</element>";
+			"</element>"
+			"<optional>"
+				"<element name='SelectionShape'>"
+					"<choice>"
+						"<attribute name='type'>"
+							"<choice>"
+								"<value>bounds</value>"
+								"<value>footprint</value>"
+							"</choice>"
+						"</attribute>"
+						"<group>"
+							"<attribute name='type'>"
+								"<text/>"
+							"</attribute>"
+							"<attribute name='width'>"
+								"<data type='decimal' />"
+							"</attribute>"
+							"<attribute name='height'>"
+								"<data type='decimal' />"
+							"</attribute>"
+							"<attribute name='depth'>"
+								"<data type='decimal' />"
+							"</attribute>"
+						"</group>"
+						"<group>"
+							"<attribute name='type'>"
+								"<text/>"
+							"</attribute>"
+							"<attribute name='radius'>"
+								"<data type='decimal' />"
+							"</attribute>"
+							"<attribute name='height'>"
+								"<data type='decimal' />"
+							"</attribute>"
+						"</group>"
+					"</choice>"
+				"</element>"
+			"</optional>";
 	}
 
 	virtual void Init(const CParamNode& paramNode)
@@ -130,8 +168,16 @@ public:
 		if (paramNode.GetChild("SilhouetteOccluder").ToBool())
 			modelFlags |= MODELFLAG_SILHOUETTE_OCCLUDER;
 
-		if (m_Unit->GetModel().ToCModel())
-			m_Unit->GetModel().ToCModel()->AddFlagsRec(modelFlags);
+		CModelAbstract& model = m_Unit->GetModel();
+		if (model.ToCModel())
+			model.ToCModel()->AddFlagsRec(modelFlags);
+
+		// Initialize the model's selection shape descriptor. This currently relies on the component initialization order; the 
+		// Footprint component must be initialized before this component (VisualActor) to support the ability to use the footprint
+		// shape for the selection box (instead of the default recursive bounding box). See TypeList.h for the order in
+		// which components are initialized; if for whatever reason you need to get rid of this dependency, you can always just
+		// initialize the selection shape descriptor on-demand.
+		InitSelectionShapeDescriptor(model, paramNode);
 
 		m_Unit->SetID(GetEntityId());
 
@@ -223,8 +269,15 @@ public:
 	virtual CBound GetBounds()
 	{
 		if (!m_Unit)
-			return CBound();
-		return m_Unit->GetModel().GetBounds();
+			return CBound::EMPTY;
+		return m_Unit->GetModel().GetWorldBounds();
+	}
+
+	virtual CBox GetSelectionBox()
+	{
+		if (!m_Unit)
+			return CBox::EMPTY;
+		return m_Unit->GetModel().GetSelectionBox();
 	}
 
 	virtual CVector3D GetPosition()
@@ -381,6 +434,13 @@ private:
 		return GetEntityId();
 	}
 
+	/// Helper method; initializes the model selection shape descriptor from XML. Factored out for readability of @ref Init.
+	/// The @p model argument is technically not really necessary since naturally this method is intended to initialize this
+	/// visual actor's model (I wouldn't know which other one you'd pass), but it's included here to enforce that the
+	/// component's model must have been created before using this method (i.e. to prevent accidentally calls to this method
+	/// before the model was constructed).
+	void InitSelectionShapeDescriptor(CModelAbstract& model, const CParamNode& paramNode);
+
 	void Update(fixed turnLength);
 	void Interpolate(float frameTime, float frameOffset);
 	void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
@@ -388,6 +448,76 @@ private:
 
 REGISTER_COMPONENT_TYPE(VisualActor)
 
+// ------------------------------------------------------------------------------------------------------------------
+
+void CCmpVisualActor::InitSelectionShapeDescriptor(CModelAbstract& model, const CParamNode& paramNode)
+{
+	// by default, we don't need a custom selection shape and we can just keep the default behaviour
+	CModelAbstract::CustomSelectionShape* shapeDescriptor = NULL;
+
+	const CParamNode& xmlSelectionShapeNode = paramNode.GetChild("SelectionShape");
+	if (xmlSelectionShapeNode.IsOk())
+	{
+		const std::wstring& selectionShapeType = xmlSelectionShapeNode.GetChild("@type").ToString(); // source attribute should always exist
+		if (selectionShapeType == L"bounds")
+		{
+			// default; no need to take action
+		}
+		else 
+		{
+			shapeDescriptor = new CModelAbstract::CustomSelectionShape;
+			if (selectionShapeType == L"footprint")
+			{
+				CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), GetEntityId());
+				if (!cmpFootprint.null())
+				{
+					ICmpFootprint::EShape fpShape;				// fp stands for "footprint"
+					entity_pos_t fpSize0, fpSize1, fpHeight;	// fp stands for "footprint"
+					cmpFootprint->GetShape(fpShape, fpSize0, fpSize1, fpHeight);
+
+					float size0 = fpSize0.ToFloat();
+					float size1 = fpSize1.ToFloat();
+
+					// TODO: we should properly distinguish between CIRCLE and SQUARE footprint shapes here, but since cylinders 
+					// aren't implemented yet (and they're almost indistinguishable from boxes for small enough sizes anyway), so 
+					// we'll just use boxes for either case. Although, as a basic measure, for circle footprints the size0 and size1
+					// represent the radius, so we do adjust that to match the (full-size) sizes of the square footprints.
+					if (fpShape == ICmpFootprint::CIRCLE)
+					{
+						size0 *= 2;
+						size1 *= 2;
+					}
+				
+					shapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
+					shapeDescriptor->m_Size0 = size0;
+					shapeDescriptor->m_Size1 = size1;
+					shapeDescriptor->m_Height = fpHeight.ToFloat();
+				}
+				else
+				{
+					LOGWARNING(L"Footprint component not yet initialized; cannot apply footprint-based SelectionShape in VisualActor");
+				}
+			}
+			else if (selectionShapeType == L"box")
+			{
+				shapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
+				shapeDescriptor->m_Size0 = xmlSelectionShapeNode.GetChild("@width").ToFixed().ToFloat();
+				shapeDescriptor->m_Size1 = xmlSelectionShapeNode.GetChild("@depth").ToFixed().ToFloat();
+				shapeDescriptor->m_Height = xmlSelectionShapeNode.GetChild("@height").ToFixed().ToFloat();
+				// TODO: we might need to support the ability to specify a different box center
+			}
+			else if (selectionShapeType == L"cylinder")
+			{
+				LOGWARNING(L"[VisualActor] TODO: cylinder selection shapes are not yet implemented; defaulting to recursive bounding boxes");
+			}
+
+		}
+	}
+
+	// the model is now responsible for cleaning up the descriptor
+	model.SetCustomSelectionShape(shapeDescriptor);
+}
+
 void CCmpVisualActor::Update(fixed turnLength)
 {
 	if (m_Unit == NULL)
@@ -482,7 +612,7 @@ void CCmpVisualActor::RenderSubmit(SceneCollector& collector, const CFrustum& fr
 
 	CModelAbstract& model = m_Unit->GetModel();
 
-	if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetBoundsRec()))
+	if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetWorldBoundsRec()))
 		return;
 
 	collector.SubmitRecursive(&model);
diff --git a/source/simulation2/components/ICmpSelectable.cpp b/source/simulation2/components/ICmpSelectable.cpp
index 20f0b09..dc959ea 100644
--- a/source/simulation2/components/ICmpSelectable.cpp
+++ b/source/simulation2/components/ICmpSelectable.cpp
@@ -26,3 +26,5 @@
 BEGIN_INTERFACE_WRAPPER(Selectable)
 DEFINE_INTERFACE_METHOD_1("SetSelectionHighlight", void, ICmpSelectable, SetSelectionHighlight, CColor)
 END_INTERFACE_WRAPPER(Selectable)
+
+bool ICmpSelectable::ms_EnableDebugOverlays = false;
\ No newline at end of file
diff --git a/source/simulation2/components/ICmpSelectable.h b/source/simulation2/components/ICmpSelectable.h
index eadd554..0ada93a 100644
--- a/source/simulation2/components/ICmpSelectable.h
+++ b/source/simulation2/components/ICmpSelectable.h
@@ -32,6 +32,8 @@ public:
 	virtual void SetSelectionHighlight(CColor color) = 0;
 
 	DECLARE_INTERFACE_TYPE(Selectable)
+
+	static bool ms_EnableDebugOverlays;
 };
 
 #endif // INCLUDED_ICMPSELECTABLE
diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h
index 68f1108..4d7a12a 100644
--- a/source/simulation2/components/ICmpVisual.h
+++ b/source/simulation2/components/ICmpVisual.h
@@ -20,6 +20,7 @@
 
 #include "simulation2/system/Interface.h"
 
+#include "maths/Box.h"
 #include "maths/Bound.h"
 #include "maths/Fixed.h"
 #include "lib/file/vfs/vfs_path.h"
@@ -37,6 +38,13 @@ public:
 	virtual CBound GetBounds() = 0;
 
 	/**
+	 * Get the oriented world-space bounding box of the object's visual representation, clipped at the Y=0 plane in object space
+	 * to prevent it from extending into the terrain. The primary difference with GetBounds is that this bounding box is not aligned 
+	 * to the world axes, but arbitrarily rotated according to the model transform.
+	 */
+	virtual CBox GetSelectionBox() = 0;
+
+	/**
 	 * Get the world-space position of the base point of the object's visual representation.
 	 * (Not safe for use in simulation code.)
 	 */
diff --git a/source/simulation2/helpers/Render.cpp b/source/simulation2/helpers/Render.cpp
index db63115..67e55df 100644
--- a/source/simulation2/helpers/Render.cpp
+++ b/source/simulation2/helpers/Render.cpp
@@ -24,9 +24,12 @@
 #include "simulation2/components/ICmpWaterManager.h"
 #include "graphics/Overlay.h"
 #include "graphics/Terrain.h"
+#include "maths/Bound.h"
+#include "maths/Box.h"
 #include "maths/MathUtil.h"
 #include "maths/Vector2D.h"
 #include "ps/Profile.h"
+#include "maths/Quaternion.h"
 
 void SimRender::ConstructLineOnGround(const CSimContext& context, const std::vector<float>& xz,
 		SOverlayLine& overlay, bool floating, float heightOffset)
@@ -159,6 +162,121 @@ void SimRender::ConstructSquareOnGround(const CSimContext& context, float x, flo
 	}
 }
 
+void SimRender::ConstructBoxOutline(const CBound& bound, SOverlayLine& overlayLine)
+{
+	overlayLine.m_Coords.clear(); 
+
+	if (bound.IsEmpty())
+		return;
+
+	const CVector3D& pMin = bound[0];
+	const CVector3D& pMax = bound[1];
+	
+	// floor square 
+	overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z);
+	overlayLine.PushCoords(pMax.X, pMin.Y, pMin.Z);
+	overlayLine.PushCoords(pMax.X, pMin.Y, pMax.Z);
+	overlayLine.PushCoords(pMin.X, pMin.Y, pMax.Z);
+	overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z);
+	// roof square 
+	overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z);
+	overlayLine.PushCoords(pMax.X, pMax.Y, pMin.Z);
+	overlayLine.PushCoords(pMax.X, pMax.Y, pMax.Z);
+	overlayLine.PushCoords(pMin.X, pMax.Y, pMax.Z);
+	overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z);
+}
+
+void SimRender::ConstructBoxOutline(const CBox& box, SOverlayLine& overlayLine)
+{
+	overlayLine.m_Coords.clear();
+
+	if (box.IsEmpty())
+		return;
+
+	CVector3D corners[8];
+	box.GetCorner(-1, -1, -1, corners[0]);
+	box.GetCorner( 1, -1, -1, corners[1]);
+	box.GetCorner( 1, -1,  1, corners[2]);
+	box.GetCorner(-1, -1,  1, corners[3]);
+	box.GetCorner(-1,  1, -1, corners[4]);
+	box.GetCorner( 1,  1, -1, corners[5]);
+	box.GetCorner( 1,  1,  1, corners[6]);
+	box.GetCorner(-1,  1,  1, corners[7]);
+
+	overlayLine.PushCoords(corners[0]);
+	overlayLine.PushCoords(corners[1]);
+	overlayLine.PushCoords(corners[2]);
+	overlayLine.PushCoords(corners[3]);
+	overlayLine.PushCoords(corners[0]);
+
+	overlayLine.PushCoords(corners[4]);
+	overlayLine.PushCoords(corners[5]);
+	overlayLine.PushCoords(corners[6]);
+	overlayLine.PushCoords(corners[7]);
+	overlayLine.PushCoords(corners[4]);
+}
+
+void SimRender::ConstructGimbal(const CVector3D& center, float radius, SOverlayLine& out, size_t numSteps)
+{
+	ENSURE(numSteps > 0 && numSteps % 4 == 0); // must be a positive multiple of 4
+	
+	out.m_Coords.clear();
+
+	size_t fullCircleSteps = numSteps;
+	const float angleIncrement = 2.f*M_PI/fullCircleSteps;
+
+	const CVector3D X_UNIT(1, 0, 0);
+	const CVector3D Y_UNIT(0, 1, 0);
+	const CVector3D Z_UNIT(0, 0, 1);
+	CVector3D rotationVector(0, 0, radius); // directional vector based in the center that we will be rotating to get the gimbal points
+
+	// first draw a quarter of XZ gimbal; then complete the XY gimbal; then continue the XZ gimbal and finally add the YZ gimbal
+	// (that way we can keep a single continuous line)
+	
+	// -- XZ GIMBAL (PART 1/2) -----------------------------------------------
+
+	CQuaternion xzRotation;
+	xzRotation.FromAxisAngle(Y_UNIT, angleIncrement);
+
+	for (size_t i = 0; i < fullCircleSteps/4; ++i) // complete only a quarter of the way
+	{
+		out.PushCoords(center + rotationVector);
+		rotationVector = xzRotation.Rotate(rotationVector);
+	}
+
+	// -- XY GIMBAL ----------------------------------------------------------
+
+	// now complete the XY gimbal while the XZ gimbal is interrupted
+	CQuaternion xyRotation;
+	xyRotation.FromAxisAngle(Z_UNIT, angleIncrement);
+
+	for (size_t i = 0; i < fullCircleSteps; ++i) // note the <; the last point of the XY gimbal isn't added, because the XZ gimbal will add it
+	{
+		out.PushCoords(center + rotationVector);
+		rotationVector = xyRotation.Rotate(rotationVector);
+	}
+
+	// -- XZ GIMBAL (PART 2/2) -----------------------------------------------
+
+	// resume the XZ gimbal to completion
+	for (size_t i = fullCircleSteps/4; i < fullCircleSteps; ++i) // exclude the last point of the circle so the YZ gimbal can add it
+	{
+		out.PushCoords(center + rotationVector);
+		rotationVector = xzRotation.Rotate(rotationVector);
+	}
+
+	// -- YZ GIMBAL ----------------------------------------------------------
+
+	CQuaternion yzRotation;
+	yzRotation.FromAxisAngle(X_UNIT, angleIncrement);
+
+	for (size_t i = 0; i <= fullCircleSteps; ++i)
+	{
+		out.PushCoords(center + rotationVector);
+		rotationVector = yzRotation.Rotate(rotationVector);
+	}
+}
+
 void SimRender::SmoothPointsAverage(std::vector<CVector2D>& points, bool closed)
 {
 	PROFILE("SmoothPointsAverage");
diff --git a/source/simulation2/helpers/Render.h b/source/simulation2/helpers/Render.h
index 0129b01..a8c0a97 100644
--- a/source/simulation2/helpers/Render.h
+++ b/source/simulation2/helpers/Render.h
@@ -25,6 +25,9 @@
 
 class CSimContext;
 class CVector2D;
+class CVector3D;
+class CBound;
+class CBox;
 struct SOverlayLine;
 
 namespace SimRender
@@ -54,6 +57,23 @@ 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.
+ */
+void ConstructBoxOutline(const CBox& box, SOverlayLine& overlayLine);
+
+/**
+ * Constructs a solid outline of an axis-aligned bounding box.
+ */
+void ConstructBoxOutline(const CBound& bound, SOverlayLine& overlayLine);
+
+/**
+ * Constructs a simple gimbal outline of radius @p radius at @p center in @p out.
+ * @param numSteps The amount of steps to trace a circle's complete outline. Must be a (strictly) positive multiple of four. 
+ *     For small radii, you can get away with small values; setting this to 4 will create a diamond shape.
+ */
+void ConstructGimbal(const CVector3D& center, float radius, SOverlayLine& out, size_t numSteps = 16);
+
+/**
  * Updates @p points so each point is averaged with its neighbours, resulting in
  * a somewhat smoother curve, assuming the points are roughly equally spaced.
  * If @p closed then the points are treated as a closed path (the last is connected
diff --git a/source/simulation2/helpers/Selection.cpp b/source/simulation2/helpers/Selection.cpp
index 0405894..d0c02e5 100644
--- a/source/simulation2/helpers/Selection.cpp
+++ b/source/simulation2/helpers/Selection.cpp
@@ -51,19 +51,18 @@ std::vector<entity_id_t> EntitySelection::PickEntitiesAtPoint(CSimulation2& simu
 		if (cmpVisual.null())
 			continue;
 
-		CBound bounds = cmpVisual->GetBounds();
+		CBox selectionBox = cmpVisual->GetSelectionBox();
+		if (selectionBox.IsEmpty())
+			continue;
 
 		float tmin, tmax;
-		if (!bounds.RayIntersect(origin, dir, tmin, tmax))
+		if (!selectionBox.RayIntersect(origin, dir, tmin, tmax))
 			continue;
 
 		// Find the perpendicular distance from the object's centre to the picker ray
 
-		CVector3D centre;
-		bounds.GetCentre(centre);
-
-		CVector3D closest = origin + dir * (centre - origin).Dot(dir);
-		float dist2 = (closest - centre).LengthSquared();
+		CVector3D closest = origin + dir * (selectionBox.m_Center - origin).Dot(dir);
+		float dist2 = (closest - selectionBox.m_Center).LengthSquared();
 
 		hits.push_back(std::make_pair(dist2, ent));
 	}
diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp
index 15f46ae..b7d3064 100644
--- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp
+++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp
@@ -44,6 +44,8 @@ enum
 	ID_ViewerShadows,
 	ID_ViewerPolyCount,
 	ID_ViewerAnimation,
+	ID_ViewerBoundingBox,
+	ID_ViewerAxesMarker,
 	ID_ViewerPlay,
 	ID_ViewerPause,
 	ID_ViewerSlow
@@ -59,10 +61,15 @@ static wxWindow* Tooltipped(wxWindow* window, const wxString& tip)
 class ObjectBottomBar : public wxPanel
 {
 public:
-	ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEditor, Observable<ObjectSettings>& objectSettings, Observable<AtObj>& mapSettings, ObjectSidebarImpl* p);
+	ObjectBottomBar(
+		wxWindow* parent,
+		ScenarioEditor& scenarioEditor,
+		Observable<ObjectSettings>& objectSettings,
+		Observable<AtObj>& mapSettings,
+		ObjectSidebarImpl* p
+	);
 
 	void OnFirstDisplay();
-
 	void ShowActorViewer(bool show);
 
 private:
@@ -75,11 +82,12 @@ private:
 	bool m_ViewerGround;
 	bool m_ViewerShadows;
 	bool m_ViewerPolyCount;
+	bool m_ViewerBoundingBox;
+	bool m_ViewerAxesMarker;
 
 	wxPanel* m_ViewerPanel;
 
 	ObjectSidebarImpl* p;
-
 	ScenarioEditor& m_ScenarioEditor;
 
 	DECLARE_EVENT_TABLE();
@@ -108,14 +116,27 @@ struct ObjectSidebarImpl
 	}
 };
 
-ObjectSidebar::ObjectSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer)
-: Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), p(new ObjectSidebarImpl())
+ObjectSidebar::ObjectSidebar(
+	ScenarioEditor& scenarioEditor,
+	wxWindow* sidebarContainer,
+	wxWindow* bottomBarContainer
+)
+	: Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer),
+	  p(new ObjectSidebarImpl())
 {
 	wxBoxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
 	sizer->Add(new wxStaticText(this, wxID_ANY, _("Filter")), wxSizerFlags().Align(wxALIGN_CENTER));
-	sizer->Add(Tooltipped(new wxTextCtrl(this, ID_ObjectFilter),
-		_("Enter text to filter object list")), wxSizerFlags().Expand().Proportion(1));
+	sizer->Add(
+		Tooltipped(
+			new wxTextCtrl(this, ID_ObjectFilter),
+			_("Enter text to filter object list")
+		),
+		wxSizerFlags().Expand().Proportion(1)
+	);
 	m_MainSizer->Add(sizer, wxSizerFlags().Expand());
+	m_MainSizer->AddSpacer(3);
+
+	// ------------------------------------------------------------------------------------------
 
 	wxArrayString strings;
 	strings.Add(_("Entities"));
@@ -123,13 +144,27 @@ ObjectSidebar::ObjectSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarCo
 	wxChoice* objectType = new wxChoice(this, ID_ObjectType, wxDefaultPosition, wxDefaultSize, strings);
 	objectType->SetSelection(0);
 	m_MainSizer->Add(objectType, wxSizerFlags().Expand());
+	m_MainSizer->AddSpacer(3);
+	
+	// ------------------------------------------------------------------------------------------
 
 	p->m_ObjectListBox = new wxListBox(this, ID_SelectObject, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE|wxLB_HSCROLL);
 	m_MainSizer->Add(p->m_ObjectListBox, wxSizerFlags().Proportion(1).Expand());
+	m_MainSizer->AddSpacer(3);
+
+	// ------------------------------------------------------------------------------------------
 
 	m_MainSizer->Add(new wxButton(this, ID_ToggleViewer, _("Switch to Actor Viewer")), wxSizerFlags().Expand());
 
-	m_BottomBar = new ObjectBottomBar(bottomBarContainer, scenarioEditor, scenarioEditor.GetObjectSettings(), scenarioEditor.GetMapSettings(), p);
+	// ------------------------------------------------------------------------------------------
+
+	m_BottomBar = new ObjectBottomBar(
+		bottomBarContainer,
+		scenarioEditor,
+		scenarioEditor.GetObjectSettings(),
+		scenarioEditor.GetMapSettings(),
+		p
+	);
 
 	p->m_ToolConn = scenarioEditor.GetToolManager().GetCurrentTool().RegisterObserver(0, &ObjectSidebar::OnToolChange, this);
 }
@@ -312,7 +347,13 @@ END_EVENT_TABLE();
 
 //////////////////////////////////////////////////////////////////////////
 
-ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEditor,  Observable<ObjectSettings>& objectSettings, Observable<AtObj>& mapSettings, ObjectSidebarImpl* p)
+ObjectBottomBar::ObjectBottomBar(
+	wxWindow* parent,
+	ScenarioEditor& scenarioEditor,
+	Observable<ObjectSettings>& objectSettings,
+	Observable<AtObj>& mapSettings,
+	ObjectSidebarImpl* p
+)
 	: wxPanel(parent, wxID_ANY), p(p), m_ScenarioEditor(scenarioEditor)
 {
 	m_ViewerWireframe = false;
@@ -320,21 +361,39 @@ ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEdito
 	m_ViewerGround = true;
 	m_ViewerShadows = true;
 	m_ViewerPolyCount = false;
+	m_ViewerBoundingBox = false;
+	m_ViewerAxesMarker = false;
 
-	wxSizer* sizer = new wxBoxSizer(wxHORIZONTAL);
+	wxSizer* mainSizer = new wxBoxSizer(wxHORIZONTAL);
 
+	// --- viewer options panel -------------------------------------------------------------------------------
 
 	m_ViewerPanel = new wxPanel(this, wxID_ANY);
 	wxSizer* viewerSizer = new wxBoxSizer(wxHORIZONTAL);
 
-	wxSizer* viewerButtonsSizer = new wxStaticBoxSizer(wxVERTICAL, m_ViewerPanel, _("Display settings"));
-	viewerButtonsSizer->SetMinSize(140, -1);
-	viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerWireframe, _("Wireframe")), _("Toggle wireframe / solid rendering")), wxSizerFlags().Expand());
-	viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerMove, _("Move")), _("Toggle movement along ground when playing walk/run animations")), wxSizerFlags().Expand());
-	viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerGround, _("Ground")), _("Toggle the ground plane")), wxSizerFlags().Expand());
-	viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerShadows, _("Shadows")), _("Toggle shadow rendering")), wxSizerFlags().Expand());
-	viewerButtonsSizer->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerPolyCount, _("Poly count")), _("Toggle polygon-count statistics - turn off ground and shadows for more useful data")), wxSizerFlags().Expand());
+	wxSizer* viewerButtonsSizer = new wxStaticBoxSizer(wxHORIZONTAL, m_ViewerPanel, _("Display settings"));
+	{
+		wxSizer* viewerButtonsLeft = new wxBoxSizer(wxVERTICAL);
+		viewerButtonsLeft->SetMinSize(110, -1);
+		viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerWireframe,   _("Wireframe")),      _("Toggle wireframe / solid rendering")), wxSizerFlags().Expand());
+		viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerMove,        _("Move")),           _("Toggle movement along ground when playing walk/run animations")), wxSizerFlags().Expand());
+		viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerGround,      _("Ground")),         _("Toggle the ground plane")), wxSizerFlags().Expand());
+		viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerShadows,     _("Shadows")),        _("Toggle shadow rendering")), wxSizerFlags().Expand());
+		viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerPolyCount,   _("Poly count")),     _("Toggle polygon-count statistics - turn off ground and shadows for more useful data")), wxSizerFlags().Expand());
+		viewerButtonsLeft->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerBoundingBox, _("Bounding Boxes")), _("Toggle bounding boxes")), wxSizerFlags().Expand());
+
+		wxSizer* viewerButtonsRight = new wxBoxSizer(wxVERTICAL);
+		viewerButtonsRight->SetMinSize(110,-1);
+		viewerButtonsRight->Add(Tooltipped(new wxButton(m_ViewerPanel, ID_ViewerAxesMarker,  _("Axes Marker")),    _("Toggle the axes marker (R=X, G=Y, B=Z)")), wxSizerFlags().Expand());
+
+		viewerButtonsSizer->Add(viewerButtonsLeft, wxSizerFlags().Expand());
+		viewerButtonsSizer->Add(viewerButtonsRight, wxSizerFlags().Expand());
+	}
+
 	viewerSizer->Add(viewerButtonsSizer, wxSizerFlags().Expand());
+	viewerSizer->AddSpacer(3);
+
+	// --- animations panel -------------------------------------------------------------------------------
 
 	wxSizer* viewerAnimSizer = new wxStaticBoxSizer(wxVERTICAL, m_ViewerPanel, _("Animation"));
 
@@ -357,17 +416,28 @@ ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEdito
 
 	viewerSizer->Add(viewerAnimSizer, wxSizerFlags().Expand());
 
+	// --- add viewer-specific options -------------------------------------------------------------------------------
+
 	m_ViewerPanel->SetSizer(viewerSizer);
-	sizer->Add(m_ViewerPanel, wxSizerFlags().Expand());
+	mainSizer->Add(m_ViewerPanel, wxSizerFlags().Expand());
 
+	m_ViewerPanel->Layout(); // prevents strange visibility glitch of the animation buttons on my machine (Vista 32-bit SP1) -- vtsj
 	m_ViewerPanel->Show(false);
 
+	// --- add player/variation selection -------------------------------------------------------------------------------
 
+	wxSizer* playerSelectionSizer = new wxBoxSizer(wxHORIZONTAL);
 	wxSizer* playerVariationSizer = new wxBoxSizer(wxVERTICAL);
 
 	// TODO: make this a wxChoice instead
 	wxComboBox* playerSelect = new PlayerComboBox(this, objectSettings, mapSettings);
-	playerVariationSizer->Add(playerSelect);
+	playerSelectionSizer->Add(new wxStaticText(this, wxID_ANY, _("Player:")), wxSizerFlags().Align(wxALIGN_CENTER));
+	playerSelectionSizer->AddSpacer(3);
+	playerSelectionSizer->Add(playerSelect);
+
+	playerVariationSizer->Add(playerSelectionSizer);
+	playerVariationSizer->AddSpacer(3);
+
 
 	wxWindow* variationSelect = new VariationControl(this, objectSettings);
 	variationSelect->SetMinSize(wxSize(160, -1));
@@ -375,9 +445,12 @@ ObjectBottomBar::ObjectBottomBar(wxWindow* parent, ScenarioEditor& scenarioEdito
 	variationSizer->Add(variationSelect, wxSizerFlags().Proportion(1).Expand());
 	playerVariationSizer->Add(variationSizer, wxSizerFlags().Proportion(1));
 
-	sizer->Add(playerVariationSizer, wxSizerFlags().Expand());
+	mainSizer->AddSpacer(3);
+	mainSizer->Add(playerVariationSizer, wxSizerFlags().Expand());
+
+	// ----------------------------------------------------------------------------------
 
-	SetSizer(sizer);
+	SetSizer(mainSizer);
 }
 
 void ObjectBottomBar::OnFirstDisplay()
@@ -402,6 +475,7 @@ void ObjectBottomBar::OnFirstDisplay()
 	POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"ground", m_ViewerGround));
 	POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"shadows", m_ViewerShadows));
 	POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount));
+	POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox));
 }
 
 void ObjectBottomBar::ShowActorViewer(bool show)
@@ -434,6 +508,14 @@ void ObjectBottomBar::OnViewerSetting(wxCommandEvent& evt)
 		m_ViewerPolyCount = !m_ViewerPolyCount;
 		POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"stats", m_ViewerPolyCount));
 		break;
+	case ID_ViewerBoundingBox:
+		m_ViewerBoundingBox = !m_ViewerBoundingBox;
+		POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"bounding_box", m_ViewerBoundingBox));
+		break;
+	case ID_ViewerAxesMarker:
+		m_ViewerAxesMarker = !m_ViewerAxesMarker;
+		POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::ACTOR, L"axes_marker", m_ViewerAxesMarker));
+		break;
 	}
 }
 
@@ -464,4 +546,6 @@ BEGIN_EVENT_TABLE(ObjectBottomBar, wxPanel)
 	EVT_BUTTON(ID_ViewerPlay, ObjectBottomBar::OnSpeed)
 	EVT_BUTTON(ID_ViewerPause, ObjectBottomBar::OnSpeed)
 	EVT_BUTTON(ID_ViewerSlow, ObjectBottomBar::OnSpeed)
+	EVT_BUTTON(ID_ViewerBoundingBox, ObjectBottomBar::OnViewerSetting)
+	EVT_BUTTON(ID_ViewerAxesMarker, ObjectBottomBar::OnViewerSetting)
 END_EVENT_TABLE();
diff --git a/source/tools/atlas/GameInterface/ActorViewer.cpp b/source/tools/atlas/GameInterface/ActorViewer.cpp
index 0a010aa..3c05af2 100644
--- a/source/tools/atlas/GameInterface/ActorViewer.cpp
+++ b/source/tools/atlas/GameInterface/ActorViewer.cpp
@@ -33,6 +33,7 @@
 #include "graphics/TerrainTextureManager.h"
 #include "graphics/TerritoryTexture.h"
 #include "graphics/UnitManager.h"
+#include "graphics/Overlay.h"
 #include "maths/MathUtil.h"
 #include "ps/Font.h"
 #include "ps/GameSetup/Config.h"
@@ -48,6 +49,7 @@
 #include "simulation2/components/ICmpTerrain.h"
 #include "simulation2/components/ICmpUnitMotion.h"
 #include "simulation2/components/ICmpVisual.h"
+#include "simulation2/helpers/Render.h"
 
 struct ActorViewerImpl : public Scene
 {
@@ -75,6 +77,8 @@ public:
 	bool WalkEnabled;
 	bool GroundEnabled;
 	bool ShadowsEnabled;
+	bool SelectionBoxEnabled;
+	bool AxesMarkerEnabled;
 
 	SColor4ub Background;
 	
@@ -89,6 +93,9 @@ public:
 	CLOSTexture LOSTexture;
 	CTerritoryTexture TerritoryTexture;
 
+	SOverlayLine SelectionBoxOverlay;
+	SOverlayLine AxesMarkerOverlays[3];
+
 	// Simplistic implementation of the Scene interface
 	virtual void EnumerateObjects(const CFrustum& frustum, SceneCollector* c)
 	{
@@ -99,6 +106,48 @@ public:
 					c->Submit(Terrain.GetPatch(pi, pj));
 		}
 
+		CmpPtr<ICmpVisual> cmpVisual(Simulation2, Entity);
+
+		// add selection box outlines manually
+		if (SelectionBoxEnabled && !cmpVisual.null())
+		{
+			SelectionBoxOverlay.m_Color = CColor(35/255.f, 86/255.f, 188/255.f, .75f); // pretty blue
+			SelectionBoxOverlay.m_Thickness = 2;
+
+			SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), SelectionBoxOverlay);
+			c->Submit(&SelectionBoxOverlay);
+		}
+
+		// add origin axis thingy
+		if (AxesMarkerEnabled && !cmpVisual.null())
+		{
+			AxesMarkerOverlays[0].m_Color = CColor(1, 0, 0, .5f); // X axis; red
+			AxesMarkerOverlays[1].m_Color = CColor(0, 1, 0, .5f); // Y axis; green
+			AxesMarkerOverlays[2].m_Color = CColor(0, 0, 1, .5f); // Z axis; blue
+
+			AxesMarkerOverlays[0].m_Thickness = 2;
+			AxesMarkerOverlays[1].m_Thickness = 2;
+			AxesMarkerOverlays[2].m_Thickness = 2;
+
+			AxesMarkerOverlays[0].m_Coords.clear();
+			AxesMarkerOverlays[1].m_Coords.clear();
+			AxesMarkerOverlays[2].m_Coords.clear();
+
+			CVector3D origin = cmpVisual->GetPosition() + CVector3D(0, 0.02f, 0); // offset from the ground a little bit to prevent fighting with the floor texture
+			AxesMarkerOverlays[0].PushCoords(origin);
+			AxesMarkerOverlays[1].PushCoords(origin);
+			AxesMarkerOverlays[2].PushCoords(origin);
+
+			AxesMarkerOverlays[0].PushCoords(origin + CVector3D(1, 0, 0));
+			AxesMarkerOverlays[1].PushCoords(origin + CVector3D(0, 1, 0));
+			AxesMarkerOverlays[2].PushCoords(origin + CVector3D(0, 0, 1));
+
+			c->Submit(&AxesMarkerOverlays[0]);
+			c->Submit(&AxesMarkerOverlays[1]);
+			c->Submit(&AxesMarkerOverlays[2]);
+		}
+
+		// send a RenderSubmit message so the components can submit their visuals to the renderer
 		Simulation2.RenderSubmit(*c, frustum, false);
 	}
 
@@ -114,11 +163,13 @@ public:
 };
 
 ActorViewer::ActorViewer()
-: m(*new ActorViewerImpl())
+	: m(*new ActorViewerImpl())
 {
 	m.WalkEnabled = false;
 	m.GroundEnabled = true;
 	m.ShadowsEnabled = g_Renderer.GetOptionBool(CRenderer::OPT_SHADOWS);
+	m.SelectionBoxEnabled = false;
+	m.AxesMarkerEnabled = false;
 	m.Background = SColor4ub(0, 0, 0, 255);
 
 	// Create a tiny empty piece of terrain, just so we can put shadows
@@ -304,6 +355,8 @@ void ActorViewer::SetBackgroundColour(const SColor4ub& colour)
 void ActorViewer::SetWalkEnabled(bool enabled)    { m.WalkEnabled = enabled; }
 void ActorViewer::SetGroundEnabled(bool enabled)  { m.GroundEnabled = enabled; }
 void ActorViewer::SetShadowsEnabled(bool enabled) { m.ShadowsEnabled = enabled; }
+void ActorViewer::SetBoundingBoxesEnabled(bool enabled) { m.SelectionBoxEnabled = enabled; }
+void ActorViewer::SetAxesMarkerEnabled(bool enabled)    { m.AxesMarkerEnabled = enabled; }
 
 void ActorViewer::SetStatsEnabled(bool enabled)
 {
diff --git a/source/tools/atlas/GameInterface/ActorViewer.h b/source/tools/atlas/GameInterface/ActorViewer.h
index 7297260..365e4e0 100644
--- a/source/tools/atlas/GameInterface/ActorViewer.h
+++ b/source/tools/atlas/GameInterface/ActorViewer.h
@@ -41,6 +41,8 @@ public:
 	void SetGroundEnabled(bool enabled);
 	void SetShadowsEnabled(bool enabled);
 	void SetStatsEnabled(bool enabled);
+	void SetBoundingBoxesEnabled(bool enabled);
+	void SetAxesMarkerEnabled(bool enabled);
 	void Render();
 	void Update(float dt);
 
diff --git a/source/tools/atlas/GameInterface/View.cpp b/source/tools/atlas/GameInterface/View.cpp
index 03a71a6..55841da 100644
--- a/source/tools/atlas/GameInterface/View.cpp
+++ b/source/tools/atlas/GameInterface/View.cpp
@@ -132,6 +132,10 @@ void ViewActor::SetParam(const std::wstring& name, bool value)
 		m_ActorViewer->SetShadowsEnabled(value);
 	else if (name == L"stats")
 		m_ActorViewer->SetStatsEnabled(value);
+	else if (name == L"bounding_box")
+		m_ActorViewer->SetBoundingBoxesEnabled(value);
+	else if (name == L"axes_marker")
+		m_ActorViewer->SetAxesMarkerEnabled(value);
 }
 
 void ViewActor::SetParam(const std::wstring& name, const AtlasMessage::Colour& value)
