diff --git a/demo/main.cpp b/demo/main.cpp
index 677e31b0a12557c49753896223570d98a77478a8..f38b2c15bbe499dfeff7f288d5e7c126c03bead4 100644
--- a/demo/main.cpp
+++ b/demo/main.cpp
@@ -107,6 +107,7 @@ int main(int argc, char* argv[]) {
 			fggl::data::make_slope( mesh, rightSlope );
 		}
 
+		mesh.removeDups();
 		auto token = meshRenderer.upload( mesh );
 		token.pipeline = shaderPhong;
 		ecs.addComponent<fggl::gfx::MeshToken>(entity, token);
diff --git a/fggl/data/model.cpp b/fggl/data/model.cpp
index e57c5c8a931421241070d22b4e6292bf921547a5..b6cb8e6462e8aa03fd8ada265627ece62faf00da 100644
--- a/fggl/data/model.cpp
+++ b/fggl/data/model.cpp
@@ -1,5 +1,8 @@
 #include <fggl/data/model.hpp>
 
+#include <map>
+#include <stdexcept>
+
 using namespace fggl::data;
 
 Mesh::Mesh() : m_verts(), m_index() {
@@ -27,6 +30,46 @@ int Mesh::indexOf(Vertex vert) {
 	return -1;
 }
 
+void Mesh::removeDups() {
+	// lookups to make detecting duplicates easier
+	std::map<Vertex, unsigned int> mapping;
+	unsigned int nextID = 0;
+
+	// new data
+	std::vector< Vertex > newVerts;
+	std::vector< unsigned int > newIndexes;
+	newIndexes.reserve( m_index.size() ); // newIndex will be same size as oldIndex
+
+	for ( auto& idx : m_index ) {
+		auto& vertex = m_verts[idx];
+		auto newID = nextID;
+		try {
+			newID = mapping.at( vertex );
+		} catch ( std::out_of_range& e ){
+			mapping[ vertex ] = newID;
+			newVerts.push_back( vertex );
+			++nextID;
+		}
+		newIndexes.push_back( newID );
+	}
+
+	// FIXME must be faster way to do this...
+	m_verts.clear();
+	m_index.clear();
+
+	for ( auto& vert : newVerts ) {
+		m_verts.push_back( vert );
+	}
+
+	for ( auto& idx : newIndexes ) {
+		m_index.push_back( idx );
+	}
+
+	// ensure we're not wasting space
+	m_verts.shrink_to_fit();
+	m_index.shrink_to_fit();
+}
+
 Model::Model() {
 }
 
diff --git a/fggl/data/model.hpp b/fggl/data/model.hpp
index ed67945573124eba2bebcbf9667168e7612eacbe..2f57d00b9def77166f3a3cd60ad8ca71770d6a18 100644
--- a/fggl/data/model.hpp
+++ b/fggl/data/model.hpp
@@ -1,6 +1,7 @@
 #ifndef FGGL_DATA_MODEL_H
 #define FGGL_DATA_MODEL_H
 
+#include <tuple>
 #include <vector>
 #include <fggl/math/types.hpp>
 
@@ -12,6 +13,23 @@ namespace fggl::data {
 		math::vec3 colour;
 	};
 
+	// comparison operators
+
+	inline bool operator<(const Vertex& lhs, const Vertex& rhs) {
+		return std::tie( lhs.posititon, lhs.normal, lhs.colour )
+		     < std::tie( rhs.posititon, rhs.normal, rhs.colour );
+	}
+
+	inline bool operator==(const Vertex& lhs, const Vertex& rhs) {
+		return lhs.posititon == rhs.posititon
+			&& lhs.colour == rhs.colour
+			&& lhs.normal == rhs.normal;
+	}
+
+	inline bool operator!=(const Vertex& lhs, const Vertex& rhs) {
+		return !(lhs==rhs);
+	}
+
 	class Mesh {
 		public:
 			Mesh();
@@ -45,6 +63,13 @@ namespace fggl::data {
 			 */
 			unsigned int pushVertex(Vertex vert);
 
+			/**
+			 * remove Duplicate verticies
+			 *
+			 * If generated using the procedural system, the mesh data can get a little messy.
+			 * This method will remove unused and duplicate verticies.
+			 */
+			void removeDups();
 
 			/**
 			 * Search for a vertex.
diff --git a/fggl/math/types.hpp b/fggl/math/types.hpp
index 32d45d308031d4d1b22fb0d4eeca3d68f0d382f3..e4b3c4612651aef354fd78c2446cc701cdbf2a15 100644
--- a/fggl/math/types.hpp
+++ b/fggl/math/types.hpp
@@ -1,6 +1,8 @@
 #ifndef FGGL_MATH_TYPES_H
 #define FGGL_MATH_TYPES_H
 
+#include <tuple>
+
 #include <glm/ext/matrix_transform.hpp>
 #include <glm/glm.hpp>
 #include <glm/gtc/quaternion.hpp>
@@ -15,6 +17,7 @@ namespace fggl::math {
 	using mat4 = glm::mat4;
 	using quat = glm::quat;
 
+
 	// reference vectors
 	constexpr vec3 UP { 0.0f, 1.0f, 0.0f };
 	constexpr vec3 FORWARD { 1.0f, 0.0f, 0.0f };
@@ -98,4 +101,14 @@ namespace fggl::math {
 
 }
 
+
+// feels a bit strange to be doing this...
+namespace glm {
+	inline bool operator<(const vec3& lhs, const vec3& rhs) {
+		return std::tie( lhs.x, lhs.y, lhs.z )
+		      < std::tie( rhs.x, rhs.y, rhs.z );
+	}
+}
+
+
 #endif