diff --git a/source/maths/BoundingBoxAligned.cpp b/source/maths/BoundingBoxAligned.cpp
index 2e0e706..80c25ef 100644
--- a/source/maths/BoundingBoxAligned.cpp
+++ b/source/maths/BoundingBoxAligned.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
@@ -217,6 +217,10 @@ void CBoundingBoxAligned::Transform(const CMatrix3D& transform, CBoundingBoxOrie
 // Intersect with the given frustum in a conservative manner
 void CBoundingBoxAligned::IntersectFrustumConservative(const CFrustum& frustum)
 {
+	// if this bound is empty, it cannot be meaningfully intersected with a frustum
+	// see http://trac.wildfiregames.com/ticket/1027
+	ENSURE(!IsEmpty());
+
 	CBrush brush(*this);
 	CBrush buf;
 
diff --git a/source/maths/BoundingBoxAligned.h b/source/maths/BoundingBoxAligned.h
index 43f0a5b..df83af6 100644
--- a/source/maths/BoundingBoxAligned.h
+++ b/source/maths/BoundingBoxAligned.h
@@ -126,6 +126,7 @@ public:
 	 *
 	 * @note While not in the spirit of this function's purpose, a no-op would be a correct
 	 * implementation of this function.
+	 * @note Must not be called if this bound is empty
 	 *
 	 * @param frustum the frustum to intersect with
 	 */
diff --git a/source/maths/Brush.cpp b/source/maths/Brush.cpp
index d6909cf..bd06280 100644
--- a/source/maths/Brush.cpp
+++ b/source/maths/Brush.cpp
@@ -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
@@ -38,11 +38,13 @@ CBrush::CBrush(const CBoundingBoxAligned& bounds)
 
 	for(size_t i = 0; i < 8; ++i)
 	{
-		m_Vertices[i][0] = bounds[(i & 1) ? 1 : 0][0];
-		m_Vertices[i][1] = bounds[(i & 2) ? 1 : 0][1];
-		m_Vertices[i][2] = bounds[(i & 4) ? 1 : 0][2];
+		m_Vertices[i][0] = bounds[(i & 1) ? 1 : 0][0]; // X
+		m_Vertices[i][1] = bounds[(i & 2) ? 1 : 0][1]; // Y
+		m_Vertices[i][2] = bounds[(i & 4) ? 1 : 0][2]; // Z
 	}
 
+	// construct cube face indices, 5 vertex indices per face (start vertex included twice)
+
 	m_Faces.resize(30);
 
 	m_Faces[0] = 0; m_Faces[1] = 1; m_Faces[2] = 3; m_Faces[3] = 2; m_Faces[4] = 0; // Z = min
@@ -69,208 +71,267 @@ void CBrush::Bounds(CBoundingBoxAligned& result) const
 
 ///////////////////////////////////////////////////////////////////////////////
 // Cut the brush according to a given plane
-struct SliceVertexInfo {
-	float d; // distance
-	size_t res; // index in result brush (or no_vertex if cut away)
-};
 
-struct NewVertexInfo {
-	size_t v1, v2; // adjacent vertices in original brush
-	size_t res; // index in result brush
+/// Holds information about what happens to a single vertex in a brush during a slicing operation.
+struct SliceOpVertexInfo
+{
+	float planeDist;    ///< Signed distance from this vertex to the slicing plane.
+	size_t resIdx;      ///< Index of this vertex in the resulting brush (or NO_VERTEX if cut away)
+};
 
-	size_t neighb1, neighb2; // index into newv
+/// Holds information about a newly introduced vertex on an edge in a brush as the result of a slicing operation.
+struct SliceOpNewVertexInfo
+{
+	/// Indices of adjacent edge vertices in original brush
+	size_t edgeIdx1, edgeIdx2;
+	/// Index of newly introduced vertex in resulting brush
+	size_t resIdx;
+	/// Index into SliceOpInfo.nvInfo; hold the indices of this new vertex's direct neighbours in the slicing plane face,
+	/// with no consistent winding direction around the face for either field (e.g., the neighb1 of X can point back to
+	/// X with either its neighb1 or neighb2).
+	size_t neighbIdx1, neighbIdx2;
 };
 
-struct SliceInfo {
-	std::vector<SliceVertexInfo> v;
-	std::vector<NewVertexInfo> newv;
-	size_t thisFaceNewVertex; // index into newv
-	const CBrush* original;
+/// Holds support information during a CBrush/CPlane slicing operation.
+struct SliceOpInfo
+{
 	CBrush* result;
+	const CBrush* original;
+
+	/// Holds information about what happens to each vertex in the original brush after the slice operation.
+	/// Same size as m_Vertices of the brush getting sliced.
+	std::vector<SliceOpVertexInfo> ovInfo;
+	/// Holds information about newly inserted vertices during a slice operation.
+	std::vector<SliceOpNewVertexInfo> nvInfo;
+	/// Indices into nvInfo; during the execution of the slicing algorithm, holds the previously inserted new vertex on 
+	/// one of the edges of the face that's currently being evaluated for slice points, or NO_VERTEX if no such vertex 
+	/// exists.
+	size_t thisFaceNewVertexIdx;
 };
 
 struct CBrush::Helper
 {
-	static size_t SliceNewVertex(SliceInfo& si, size_t v1, size_t v2);
+	/// Creates a new vertex between the given two vertices (indexed into the original brush).
+	/// Returns the index of the new vertex in the resulting brush.
+	static size_t SliceNewVertex(SliceOpInfo& sliceInfo, size_t v1, size_t v2);
 };
 
-// create a new vertex between the given two vertices (index into original brush)
-// returns the index of the new vertex in the resulting brush
-size_t CBrush::Helper::SliceNewVertex(SliceInfo& si, size_t v1, size_t v2)
+size_t CBrush::Helper::SliceNewVertex(SliceOpInfo& sliceOp, size_t edgeIdx1, size_t edgeIdx2)
 {
+	// check if a new vertex has already been inserted on this edge
 	size_t idx;
-
-	for(idx = 0; idx < si.newv.size(); ++idx)
+	for(idx = 0; idx < sliceOp.nvInfo.size(); ++idx)
 	{
-		if ((si.newv[idx].v1 == v1 && si.newv[idx].v2 == v2) ||
-		    (si.newv[idx].v1 == v2 && si.newv[idx].v2 == v1))
+		if ((sliceOp.nvInfo[idx].edgeIdx1 == edgeIdx1 && sliceOp.nvInfo[idx].edgeIdx2 == edgeIdx2) ||
+		    (sliceOp.nvInfo[idx].edgeIdx1 == edgeIdx2 && sliceOp.nvInfo[idx].edgeIdx2 == edgeIdx1))
 			break;
 	}
 
-	if (idx >= si.newv.size())
+	if (idx >= sliceOp.nvInfo.size())
 	{
-		NewVertexInfo nvi;
-		CVector3D newpos;
-		float inv = 1.0 / (si.v[v1].d - si.v[v2].d);
-
-		newpos = si.original->m_Vertices[v2]*(si.v[v1].d*inv) +
-			 si.original->m_Vertices[v1]*(-si.v[v2].d*inv);
-
-		nvi.v1 = v1;
-		nvi.v2 = v2;
-		nvi.res = si.result->m_Vertices.size();
-		nvi.neighb1 = no_vertex;
-		nvi.neighb2 = no_vertex;
-		si.result->m_Vertices.push_back(newpos);
-		si.newv.push_back(nvi);
+		// no previously inserted new vertex found on this edge; insert a new one
+		SliceOpNewVertexInfo nvi;
+		CVector3D newPos;
+		
+		// interpolate between the two vertices based on their distance from the plane
+		float inv = 1.0 / (sliceOp.ovInfo[edgeIdx1].planeDist - sliceOp.ovInfo[edgeIdx2].planeDist);
+		newPos = sliceOp.original->m_Vertices[edgeIdx2] * ( sliceOp.ovInfo[edgeIdx1].planeDist * inv) +
+		         sliceOp.original->m_Vertices[edgeIdx1] * (-sliceOp.ovInfo[edgeIdx2].planeDist * inv);
+
+		nvi.edgeIdx1 = edgeIdx1;
+		nvi.edgeIdx2 = edgeIdx2;
+		nvi.resIdx = sliceOp.result->m_Vertices.size();
+		nvi.neighbIdx1 = NO_VERTEX;
+		nvi.neighbIdx2 = NO_VERTEX;
+
+		sliceOp.result->m_Vertices.push_back(newPos);
+		sliceOp.nvInfo.push_back(nvi);
 	}
 
-	if (si.thisFaceNewVertex != no_vertex)
+	// at this point, 'idx' is the index into nvInfo of the vertex inserted onto the edge
+
+	if (sliceOp.thisFaceNewVertexIdx != NO_VERTEX)
 	{
-		if (si.newv[si.thisFaceNewVertex].neighb1 == no_vertex)
-			si.newv[si.thisFaceNewVertex].neighb1 = idx;
+		// a vertex has been previously inserted onto another edge of this face; link them together as neighbours
+		// (using whichever one of the neighbIdx1 or -2 links is still available)
+
+		if (sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx1 == NO_VERTEX)
+			sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx1 = idx;
 		else
-			si.newv[si.thisFaceNewVertex].neighb2 = idx;
+			sliceOp.nvInfo[sliceOp.thisFaceNewVertexIdx].neighbIdx2 = idx;
 
-		if (si.newv[idx].neighb1 == no_vertex)
-			si.newv[idx].neighb1 = si.thisFaceNewVertex;
+		if (sliceOp.nvInfo[idx].neighbIdx1 == NO_VERTEX)
+			sliceOp.nvInfo[idx].neighbIdx1 = sliceOp.thisFaceNewVertexIdx;
 		else
-			si.newv[idx].neighb2 = si.thisFaceNewVertex;
+			sliceOp.nvInfo[idx].neighbIdx2 = sliceOp.thisFaceNewVertexIdx;
 
-		si.thisFaceNewVertex = no_vertex;
+		// a plane should slice a face only in two locations, so reset for the next face
+		sliceOp.thisFaceNewVertexIdx = NO_VERTEX;
 	}
 	else
 	{
-		si.thisFaceNewVertex = idx;
+		// store the index of the inserted vertex on this edge, so that we can retrieve it when the plane slices
+		// this face again in another edge
+		sliceOp.thisFaceNewVertexIdx = idx;
 	}
 
-	return si.newv[idx].res;
+	return sliceOp.nvInfo[idx].resIdx;
 }
 
 void CBrush::Slice(const CPlane& plane, CBrush& result) const
 {
 	ENSURE(&result != this);
 
-	SliceInfo si;
+	SliceOpInfo sliceOp;
 
-	si.original = this;
-	si.result = &result;
-	si.thisFaceNewVertex = no_vertex;
-	si.newv.reserve(m_Vertices.size() / 2);
+	sliceOp.original = this;
+	sliceOp.result = &result;
+	sliceOp.thisFaceNewVertexIdx = NO_VERTEX;
+	sliceOp.ovInfo.resize(m_Vertices.size());
+	sliceOp.nvInfo.reserve(m_Vertices.size() / 2);
 
 	result.m_Vertices.resize(0); // clear any left-overs
 	result.m_Faces.resize(0);
 	result.m_Vertices.reserve(m_Vertices.size() + 2);
 	result.m_Faces.reserve(m_Faces.size() + 5);
 
-	// Classify and copy vertices
-	si.v.resize(m_Vertices.size());
-
+	// Copy vertices that weren't sliced away by the plane to the resulting brush.
 	for(size_t i = 0; i < m_Vertices.size(); ++i)
 	{
-		si.v[i].d = plane.DistanceToPlane(m_Vertices[i]);
-		if (si.v[i].d >= 0.0)
+		const CVector3D& vtx = m_Vertices[i];            // current vertex
+		SliceOpVertexInfo& vtxInfo = sliceOp.ovInfo[i];  // slicing operation info about current vertex
+
+		vtxInfo.planeDist = plane.DistanceToPlane(vtx);
+		if (vtxInfo.planeDist >= 0.0)
 		{
-			si.v[i].res = result.m_Vertices.size();
-			result.m_Vertices.push_back(m_Vertices[i]);
+			// positive side of the plane; not sliced away
+			vtxInfo.resIdx = result.m_Vertices.size();
+			result.m_Vertices.push_back(vtx);
 		}
 		else
 		{
-			si.v[i].res = no_vertex;
+			// other side of the plane; sliced away
+			vtxInfo.resIdx = NO_VERTEX;
 		}
 	}
 
-	// Transfer faces
-	size_t firstInFace = no_vertex; // in original brush
-	size_t startInResultFaceArray = ~0u;
+	// Transfer faces. (Recall how faces are specified; see CBrush::m_Faces). The idea is to examine each face separately,
+	// and see where its edges cross the slicing plane (meaning that exactly one of the vertices of that edge was cut away).
+	// On those edges, new vertices are introduced where the edge intersects the plane, and the resulting brush's m_Faces 
+	// array is updated to refer to the newly inserted vertices instead of the original one that got cut away.
+
+	size_t currentFaceStartIdx = NO_VERTEX; // index of the first vertex of the current face in the original brush
+	size_t resultFaceStartIdx = NO_VERTEX;  // index of the first vertex of the current face in the resulting brush
 
 	for(size_t i = 0; i < m_Faces.size(); ++i)
 	{
-		if (firstInFace == no_vertex)
+		if (currentFaceStartIdx == NO_VERTEX)
 		{
-			ENSURE(si.thisFaceNewVertex == no_vertex);
+			// starting a new face
+			ENSURE(sliceOp.thisFaceNewVertexIdx == NO_VERTEX);
 
-			firstInFace = m_Faces[i];
-			startInResultFaceArray = result.m_Faces.size();
+			currentFaceStartIdx = m_Faces[i];
+			resultFaceStartIdx = result.m_Faces.size();
 			continue;
 		}
 
-		size_t prev = m_Faces[i-1];
-		size_t cur = m_Faces[i];
+		size_t prevIdx = m_Faces[i-1];  // index of previous vertex in this face list
+		size_t curIdx = m_Faces[i];     // index of current vertex in this face list
 
-		if (si.v[prev].res == no_vertex)
+		if (sliceOp.ovInfo[prevIdx].resIdx == NO_VERTEX)
 		{
-			if (si.v[cur].res != no_vertex)
+			// previous face vertex got sliced away by the plane; see if the edge (prev,current) crosses the slicing plane
+			if (sliceOp.ovInfo[curIdx].resIdx != NO_VERTEX)
 			{
-				// re-entering the front side of the plane
-				result.m_Faces.push_back(Helper::SliceNewVertex(si, prev, cur));
-				result.m_Faces.push_back(si.v[cur].res);
+				// re-entering the front side of the plane; insert vertex on intersection of plane and (prev,current) edge
+				result.m_Faces.push_back(Helper::SliceNewVertex(sliceOp, prevIdx, curIdx));
+				result.m_Faces.push_back(sliceOp.ovInfo[curIdx].resIdx);
 			}
 		}
 		else
 		{
-			if (si.v[cur].res != no_vertex)
+			// previous face vertex didn't get sliced away; see if the edge (prev,current) crosses the slicing plane
+			if (sliceOp.ovInfo[curIdx].resIdx != NO_VERTEX)
 			{
-				// perfectly normal edge
-				result.m_Faces.push_back(si.v[cur].res);
+				// perfectly normal edge; doesn't cross the plane
+				result.m_Faces.push_back(sliceOp.ovInfo[curIdx].resIdx);
 			}
 			else
 			{
-				// leaving the front side of the plane
-				result.m_Faces.push_back(Helper::SliceNewVertex(si, prev, cur));
+				// leaving the front side of the plane; insert vertex on intersection of plane and edge (prev, current)
+				result.m_Faces.push_back(Helper::SliceNewVertex(sliceOp, prevIdx, curIdx));
 			}
 		}
 
-		if (cur == firstInFace)
+		// if we're back at the first vertex of the current face, then we've completed the face
+		if (curIdx == currentFaceStartIdx)
 		{
-			if (result.m_Faces.size() > startInResultFaceArray)
-				result.m_Faces.push_back(result.m_Faces[startInResultFaceArray]);
-			firstInFace = no_vertex; // start a new face
+			// close the index loop
+			if (result.m_Faces.size() > resultFaceStartIdx)
+				result.m_Faces.push_back(result.m_Faces[resultFaceStartIdx]);
+
+			currentFaceStartIdx = NO_VERTEX; // start a new face
 		}
 	}
 
-	ENSURE(firstInFace == no_vertex);
+	ENSURE(currentFaceStartIdx == NO_VERTEX);
+
+	// Create the face that lies in the slicing plane. Remember, all the intersections of the slicing plane with face
+	// edges of the brush have been stored in sliceOp.nvInfo by the SliceNewVertex function, and refer to their direct 
+	// neighbours in the slicing plane face using the neighbIdx1 and neighbIdx2 fields (in no consistent winding order).
 
-	// Create the face that lies in the slicing plane
-	if (si.newv.size())
+	if (sliceOp.nvInfo.size())
 	{
-		size_t prev = 0;
+		// push the starting vertex
+		result.m_Faces.push_back(sliceOp.nvInfo[0].resIdx);
+		
+		// At this point, there is no consistent winding order in the neighbX fields, so at each vertex we need to figure 
+		// out whether neighb1 or neighb2 points 'onwards' along the face, according to an initially chosen winding direction.
+		// (or, equivalently, which one points back to the one we were just at). At each vertex, we then set neighb1 to be the 
+		// one to point onwards, deleting any pointers which we no longer need to complete the trace.
+
 		size_t idx;
+		size_t prev = 0;
 
-		result.m_Faces.push_back(si.newv[0].res);
-		idx = si.newv[0].neighb2;
-		si.newv[0].neighb2 = no_vertex;
+		idx = sliceOp.nvInfo[0].neighbIdx2; // pick arbitrary starting direction
+		sliceOp.nvInfo[0].neighbIdx2 = NO_VERTEX;
 
 		while(idx != 0)
 		{
-			ENSURE(idx < si.newv.size());
-			if (idx >= si.newv.size())
+			ENSURE(idx < sliceOp.nvInfo.size());
+			if (idx >= sliceOp.nvInfo.size())
 				break;
 
-			if (si.newv[idx].neighb1 == prev)
+			if (sliceOp.nvInfo[idx].neighbIdx1 == prev)
 			{
-				si.newv[idx].neighb1 = si.newv[idx].neighb2;
-				si.newv[idx].neighb2 = no_vertex;
+				// neighb1 is pointing the wrong way; we want to normalize it to point onwards in the direction
+				// we initially chose, so swap it with neighb2 and delete neighb2 (no longer needed)
+				sliceOp.nvInfo[idx].neighbIdx1 = sliceOp.nvInfo[idx].neighbIdx2;
+				sliceOp.nvInfo[idx].neighbIdx2 = NO_VERTEX;
 			}
 			else
 			{
-				ENSURE(si.newv[idx].neighb2 == prev);
-
-				si.newv[idx].neighb2 = no_vertex;
+				// neighb1 isn't pointing to the previous vertex, so neighb2 must be (otherwise a pair of vertices failed to
+				// get paired properly during face/plane slicing).
+				ENSURE(sliceOp.nvInfo[idx].neighbIdx2 == prev);
+				sliceOp.nvInfo[idx].neighbIdx2 = NO_VERTEX;
 			}
 
-			result.m_Faces.push_back(si.newv[idx].res);
+			result.m_Faces.push_back(sliceOp.nvInfo[idx].resIdx);
 
+			// move to next vertex; neighb1 has been normalized to point onward
 			prev = idx;
-			idx = si.newv[idx].neighb1;
-			si.newv[prev].neighb1 = no_vertex;
+			idx = sliceOp.nvInfo[idx].neighbIdx1;
+			sliceOp.nvInfo[prev].neighbIdx1 = NO_VERTEX; // no longer needed, we've moved on
 		}
 
-		result.m_Faces.push_back(si.newv[0].res);
+		// push starting vertex again to close the shape
+		result.m_Faces.push_back(sliceOp.nvInfo[0].resIdx);
 	}
 }
 
 
+
 ///////////////////////////////////////////////////////////////////////////////
 // Intersect with frustum by repeated slicing
 void CBrush::Intersect(const CFrustum& frustum, CBrush& result) const
@@ -287,6 +348,9 @@ void CBrush::Intersect(const CFrustum& frustum, CBrush& result) const
 	const CBrush* prev = this;
 	CBrush* next;
 
+	// Repeatedly slice this brush with each plane of the frustum, alternating between 'result' and 'buf' to 
+	// save intermediate results. Set up the starting brush so that the final version always ends up in 'result'.
+
 	if (frustum.GetNumPlanes() & 1)
 		next = &result;
 	else
@@ -304,3 +368,39 @@ void CBrush::Intersect(const CFrustum& frustum, CBrush& result) const
 
 	ENSURE(prev == &result);
 }
\ No newline at end of file
+
+std::vector<CVector3D> CBrush::GetVertices() const 
+{
+	return m_Vertices;
+}
+
+void CBrush::GetFaces(std::vector<std::vector<size_t> >& out) const
+{
+	// split the back-to-back faces into separate face vectors, so that they're in a 
+	// user-friendlier format than the back-to-back vertex index array
+	// i.e. split 'x--xy------yz----z' into 'x--x', 'y-------y', 'z---z'
+
+	size_t faceStartIdx = 0;
+	while (faceStartIdx < m_Faces.size())
+	{
+		// start new face
+		std::vector<size_t> singleFace;
+		singleFace.push_back(m_Faces[faceStartIdx]);
+
+		// step over all the values in the face until we hit the starting value again (which closes the face)
+		size_t j = faceStartIdx + 1;
+		while (j < m_Faces.size() && m_Faces[j] != m_Faces[faceStartIdx])
+		{
+			singleFace.push_back(m_Faces[j]);
+			j++;
+		}
+
+		// each face must be closed by the same value that started it
+		ENSURE(m_Faces[faceStartIdx] == m_Faces[j]);
+
+		singleFace.push_back(m_Faces[j]);
+		out.push_back(singleFace);
+
+		faceStartIdx = j + 1;
+	}
+}
\ No newline at end of file
diff --git a/source/maths/Brush.h b/source/maths/Brush.h
index bc87383..26996d8 100644
--- a/source/maths/Brush.h
+++ b/source/maths/Brush.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
@@ -59,8 +59,9 @@ public:
 	void Bounds(CBoundingBoxAligned& result) const;
 
 	/**
-	 * Slice: Cut the object along the given plane, resulting in a smaller (or even empty)
-	 * brush representing the part of the object that lies in front of the plane.
+	 * Slice: Cut the object along the given plane, resulting in a smaller (or even empty) brush representing 
+	 * the part of the object that lies in front of the plane (as defined by the positive direction of its 
+	 * normal vector).
 	 *
 	 * @param plane the slicing plane
 	 * @param result the resulting brush is stored here
@@ -75,13 +76,31 @@ public:
 	 */
 	void Intersect(const CFrustum& frustum, CBrush& result) const;
 
+	/**
+	 * Returns a copy of the vertices in this brush. Intended for testing purposes; you should not need to use
+	 * this method directly.
+	 */
+	std::vector<CVector3D> GetVertices() const;
+
+	/**
+	 * Writes a vector of the faces in this brush to @p out. Each face is itself a vector, listing the vertex indices 
+	 * that make up the face, starting and ending with the same index. Intended for testing purposes; you should not 
+	 * need to use this method directly.
+	 */
+	void GetFaces(std::vector<std::vector<size_t> >& out) const;
+
 private:
-	static const size_t no_vertex = ~0u;
+	static const size_t NO_VERTEX = ~0u;
 
 	typedef std::vector<CVector3D> Vertices;
 	typedef std::vector<size_t> FaceIndices;
 
+	/// Collection of vertices that make up this shape.
 	Vertices m_Vertices;
+
+	/// Holds the face definitions of this brush. Each face is a sequence of indices into m_Vertices that starts and ends with 
+	/// the same vertex index, completing a loop through all the vertices that make up the face. This vector holds all the face
+	/// sequences back-to-back, thus looking something like 'x---xy--------yz--z' in the general case.
 	FaceIndices m_Faces;
 
 	struct Helper;
diff --git a/source/maths/tests/test_Bound.h b/source/maths/tests/test_Bound.h
index 54d3c73..282f54f 100644
--- a/source/maths/tests/test_Bound.h
+++ b/source/maths/tests/test_Bound.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
@@ -18,11 +18,12 @@
 #include "lib/self_test.h"
 
 #include "maths/BoundingBoxAligned.h"
+#include "maths/BoundingBoxOriented.h"
 
 class TestBound : public CxxTest::TestSuite 
 {
 public:
-	void test_empty()
+	void test_empty_aabb()
 	{
 		CBoundingBoxAligned bound;
 		TS_ASSERT(bound.IsEmpty());
@@ -32,6 +33,19 @@ public:
 		TS_ASSERT(bound.IsEmpty());
 	}
 
+	void test_empty_obb()
+	{
+		CBoundingBoxOriented bound;
+		TS_ASSERT(bound.IsEmpty());
+		bound.m_Basis[0] = CVector3D(1,0,0);
+		bound.m_Basis[1] = CVector3D(0,1,0);
+		bound.m_Basis[2] = CVector3D(0,0,1);
+		bound.m_HalfSizes = CVector3D(1,2,3);
+		TS_ASSERT(!bound.IsEmpty());
+		bound.SetEmpty();
+		TS_ASSERT(bound.IsEmpty());
+	}
+
 	void test_extend_vector()
 	{
 		CBoundingBoxAligned bound;
diff --git a/source/maths/tests/test_Brush.h b/source/maths/tests/test_Brush.h
new file mode 100644
index 0000000..ffbe840
--- /dev/null
+++ b/source/maths/tests/test_Brush.h
@@ -0,0 +1,124 @@
+/* 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/>.
+ */
+
+#include "lib/self_test.h"
+
+#include "maths/Brush.h"
+#include "maths/BoundingBoxAligned.h"
+#include "graphics/Frustum.h"
+
+class TestBrush : public CxxTest::TestSuite 
+{
+public:
+	void setUp()
+	{
+		CxxTest::setAbortTestOnFail(true);
+	}
+
+	void tearDown()
+	{
+
+	}
+
+	void test_slice_plane_simple()
+	{
+		// slice a 1x1x1 cube vertically down the middle at z = 0.5, with the plane normal pointing towards the negative
+		// end of the Z axis (i.e., anything with Z lower than 0.5 is considered 'in front of' the plane and is kept)
+		CPlane plane(CVector4D(0, 0, -1, 0.5f));
+		CBrush brush(CBoundingBoxAligned(CVector3D(0,0,0), CVector3D(1,1,1)));
+
+		CBrush result;
+		brush.Slice(plane, result);
+
+		// verify that the resulting brush consists of exactly our 8 expected, unique vertices
+		TS_ASSERT_EQUALS(8, result.GetVertices().size());
+		size_t LBF = GetUniqueVertexIndex(result, CVector3D(0, 0, 0)); // left-bottom-front <=> XYZ
+		size_t RBF = GetUniqueVertexIndex(result, CVector3D(1, 0, 0)); // right-bottom-front
+		size_t RBB = GetUniqueVertexIndex(result, CVector3D(1, 0, 0.5f)); // right-bottom-back
+		size_t LBB = GetUniqueVertexIndex(result, CVector3D(0, 0, 0.5f)); // etc.
+		size_t LTF = GetUniqueVertexIndex(result, CVector3D(0, 1, 0));
+		size_t RTF = GetUniqueVertexIndex(result, CVector3D(1, 1, 0));
+		size_t RTB = GetUniqueVertexIndex(result, CVector3D(1, 1, 0.5f));
+		size_t LTB = GetUniqueVertexIndex(result, CVector3D(0, 1, 0.5f));
+
+		// verify that the brush contains the six expected planes (one of which is the slicing plane)
+		VerifyFacePresent(result, 5, LBF, RBF, RBB, LBB, LBF); // bottom face
+		VerifyFacePresent(result, 5, LTF, RTF, RTB, LTB, LTF); // top face
+		VerifyFacePresent(result, 5, LBF, LBB, LTB, LTF, LBF); // left face
+		VerifyFacePresent(result, 5, RBF, RBB, RTB, RTF, RBF); // right face
+		VerifyFacePresent(result, 5, LBF, RBF, RTF, LTF, LBF); // front face
+		VerifyFacePresent(result, 5, LBB, RBB, RTB, LTB, LBB); // back face
+	}
+
+private:
+	size_t GetUniqueVertexIndex(const CBrush& brush, const CVector3D& vertex, float eps = 1e-6f)
+	{
+		std::vector<CVector3D> vertices = brush.GetVertices();
+
+		for (size_t i = 0; i < vertices.size(); ++i)
+		{
+			const CVector3D& v = vertices[i];
+			if (fabs(v.X - vertex.X) < eps
+				&& fabs(v.Y - vertex.Y) < eps
+				&& fabs(v.Z - vertex.Z) < eps)
+				return i;
+		}
+
+		TS_FAIL("Vertex not found in brush");
+		return ~0u;
+	}
+
+	void VerifyFacePresent(const CBrush& brush, int count, ...)
+	{
+		std::vector<size_t> face;
+
+		va_list args;
+		va_start(args, count);
+		for (int x = 0; x < count; ++x)
+			face.push_back(va_arg(args, size_t));
+		va_end(args);
+
+		if (face.size() == 0)
+			return;
+
+		std::vector<std::vector<size_t> > faces;
+		brush.GetFaces(faces);
+
+		// the brush is free to use any starting vertex along the face, and to use any winding order, so have 'face' 
+		// cycle through various starting values and see if any of them (or their reverse) matches one found in the brush.
+		
+		for (size_t c = 0; c < face.size() - 1; ++c)
+		{
+			std::vector<std::vector<size_t> >::iterator it1 = std::find(faces.begin(), faces.end(), face);
+			if (it1 != faces.end())
+				return;
+			
+			// no match, try the reverse
+			std::vector<size_t> faceReverse = face;
+			std::reverse(faceReverse.begin(), faceReverse.end());
+			std::vector<std::vector<size_t> >::iterator it2 = std::find(faces.begin(), faces.end(), faceReverse);
+			if (it2 != faces.end())
+				return;
+
+			// no match, cycle it
+			face.erase(face.begin());
+			face.push_back(face[0]);
+		}
+
+		TS_FAIL("Face not found in brush");
+	}
+};
diff --git a/source/renderer/ShadowMap.cpp b/source/renderer/ShadowMap.cpp
index 779366d..30685ce 100644
--- a/source/renderer/ShadowMap.cpp
+++ b/source/renderer/ShadowMap.cpp
@@ -218,71 +218,79 @@ void ShadowMap::AddShadowedBound(const CBoundingBoxAligned& bounds)
 // projection and transformation matrices
 void ShadowMapInternals::CalcShadowMatrices()
 {
-	CRenderer& renderer = g_Renderer;
-
-	float minZ = ShadowBound[0].Z;
-
-	ShadowBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum());
-
-	// round off the shadow boundaries to sane increments to help reduce swim effect
-	float boundInc = 16.0f;
-	ShadowBound[0].X = floor(ShadowBound[0].X / boundInc) * boundInc;
-	ShadowBound[0].Y = floor(ShadowBound[0].Y / boundInc) * boundInc;
-	ShadowBound[1].X = ceil(ShadowBound[1].X / boundInc) * boundInc;
-	ShadowBound[1].Y = ceil(ShadowBound[1].Y / boundInc) * boundInc;
-
-	// minimum Z bound must not be clipped too much, because objects that lie outside
-	// the shadow bounds cannot cast shadows either
-	// the 2.0 is rather arbitrary: it should be big enough so that we won't accidently miss
-	// a shadow generator, and small enough not to affect Z precision
-	ShadowBound[0].Z = minZ - 2.0;
-
-	// Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering
-	CVector3D scale = ShadowBound[1] - ShadowBound[0];
-	CVector3D shift = (ShadowBound[1] + ShadowBound[0]) * -0.5;
-
-	if (scale.X < 1.0)
-		scale.X = 1.0;
-	if (scale.Y < 1.0)
-		scale.Y = 1.0;
-	if (scale.Z < 1.0)
-		scale.Z = 1.0;
-
-	scale.X = 2.0 / scale.X;
-	scale.Y = 2.0 / scale.Y;
-	scale.Z = 2.0 / scale.Z;
-
-	// make sure a given world position falls on a consistent shadowmap texel fractional offset
-	float offsetX = fmod(ShadowBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth));
-	float offsetY = fmod(ShadowBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight));
-
-	LightProjection.SetZero();
-	LightProjection._11 = scale.X;
-	LightProjection._14 = (shift.X + offsetX) * scale.X;
-	LightProjection._22 = scale.Y;
-	LightProjection._24 = (shift.Y + offsetY) * scale.Y;
-	LightProjection._33 = scale.Z;
-	LightProjection._34 = shift.Z * scale.Z + renderer.m_ShadowZBias;
-	LightProjection._44 = 1.0;
-
-
-	// Calculate texture matrix by creating the clip space to texture coordinate matrix
-	// and then concatenating all matrices that have been calculated so far
-	CMatrix3D lightToTex;
-	float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width;
-	float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height;
-	float texscalez = scale.Z * 0.5f;
-
-	lightToTex.SetZero();
-	lightToTex._11 = texscalex;
-	lightToTex._14 = (offsetX - ShadowBound[0].X) * texscalex;
-	lightToTex._22 = texscaley;
-	lightToTex._24 = (offsetY - ShadowBound[0].Y) * texscaley;
-	lightToTex._33 = texscalez;
-	lightToTex._34 = -ShadowBound[0].Z * texscalez;
-	lightToTex._44 = 1.0;
-
-	TextureMatrix = lightToTex * LightTransform;
+	if (!ShadowBound.IsEmpty())
+	{
+		CRenderer& renderer = g_Renderer;
+		float minZ = ShadowBound[0].Z;
+
+		ShadowBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum());
+
+		// round off the shadow boundaries to sane increments to help reduce swim effect
+		float boundInc = 16.0f;
+		ShadowBound[0].X = floor(ShadowBound[0].X / boundInc) * boundInc;
+		ShadowBound[0].Y = floor(ShadowBound[0].Y / boundInc) * boundInc;
+		ShadowBound[1].X = ceil(ShadowBound[1].X / boundInc) * boundInc;
+		ShadowBound[1].Y = ceil(ShadowBound[1].Y / boundInc) * boundInc;
+
+		// minimum Z bound must not be clipped too much, because objects that lie outside
+		// the shadow bounds cannot cast shadows either
+		// the 2.0 is rather arbitrary: it should be big enough so that we won't accidently miss
+		// a shadow generator, and small enough not to affect Z precision
+		ShadowBound[0].Z = minZ - 2.0;
+
+		// Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering
+		CVector3D scale = ShadowBound[1] - ShadowBound[0];
+		CVector3D shift = (ShadowBound[1] + ShadowBound[0]) * -0.5;
+
+		if (scale.X < 1.0)
+			scale.X = 1.0;
+		if (scale.Y < 1.0)
+			scale.Y = 1.0;
+		if (scale.Z < 1.0)
+			scale.Z = 1.0;
+
+		scale.X = 2.0 / scale.X;
+		scale.Y = 2.0 / scale.Y;
+		scale.Z = 2.0 / scale.Z;
+
+		// make sure a given world position falls on a consistent shadowmap texel fractional offset
+		float offsetX = fmod(ShadowBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth));
+		float offsetY = fmod(ShadowBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight));
+
+		LightProjection.SetZero();
+		LightProjection._11 = scale.X;
+		LightProjection._14 = (shift.X + offsetX) * scale.X;
+		LightProjection._22 = scale.Y;
+		LightProjection._24 = (shift.Y + offsetY) * scale.Y;
+		LightProjection._33 = scale.Z;
+		LightProjection._34 = shift.Z * scale.Z + renderer.m_ShadowZBias;
+		LightProjection._44 = 1.0;
+
+		// Calculate texture matrix by creating the clip space to texture coordinate matrix
+		// and then concatenating all matrices that have been calculated so far
+
+		float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width;
+		float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height;
+		float texscalez = scale.Z * 0.5f;
+
+		CMatrix3D lightToTex;
+		lightToTex.SetZero();
+		lightToTex._11 = texscalex;
+		lightToTex._14 = (offsetX - ShadowBound[0].X) * texscalex;
+		lightToTex._22 = texscaley;
+		lightToTex._24 = (offsetY - ShadowBound[0].Y) * texscaley;
+		lightToTex._33 = texscalez;
+		lightToTex._34 = -ShadowBound[0].Z * texscalez;
+		lightToTex._44 = 1.0;
+
+		TextureMatrix = lightToTex * LightTransform;
+	}
+	else
+	{
+		// TODO: what are sensible no-op values here?
+		LightProjection.SetIdentity();
+		TextureMatrix = LightTransform;
+	}
 }
 
 
