diff --git a/build.sh b/build.sh
index 9e20a961c782d0d331c13772692930e21e656002..0fc82c0d206cd049ea486d8ef44b29d943cb1ef7 100755
--- a/build.sh
+++ b/build.sh
@@ -43,5 +43,5 @@ fi
 #EXE="gdb $EXE"
 
 pushd demo
-$EXE ../build/demo/FgglDemo > /tmp/fggl.log 2>&1
+$EXE gdb ../build/demo/FgglDemo
 popd
diff --git a/demo/main.cpp b/demo/main.cpp
index 7c3b7f13161b1b808f87f0345d8bc4733e8cee3b..d865c9897f0b5137a5038126b01ea69c34db7cb8 100644
--- a/demo/main.cpp
+++ b/demo/main.cpp
@@ -9,6 +9,8 @@
 
 #include <fggl/ecs2/ecs.cpp>
 #include <fggl/gfx/ecs.hpp>
+#include <fggl/gfx/ecs-cam.hpp>
+
 #include <fggl/math/ecs.hpp>
 
 #include <fggl/data/procedural.hpp>
@@ -50,206 +52,6 @@ void discover(std::filesystem::path base) {
 
 }
 
-enum camera_type { cam_free, cam_arcball };
-camera_type cam_mode = cam_free;
-
-struct CameraHacks {
-	fggl::gfx::Window& window;
-	fggl::gfx::Input& input;
-};
-
-void CameraArcball(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c);
-void CameraFree(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c);
-void CameraEdgeScroll(fggl::gfx::Input& input, fggl::components::Transform& t, fggl::components::Camera& c);
-
-//TODO proper input system
-void CameraInput(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c) {
-	CameraHacks* hacks = (CameraHacks*)( it.ctx() );
-	fggl::gfx::Window& window = hacks->window;
-	fggl::gfx::Input& input = hacks->input;
-
-	if ( glfwGetKey(window.handle(), GLFW_KEY_F2) == GLFW_PRESS ) {
-		cam_mode = cam_free; 
-	}
-	if ( glfwGetKey(window.handle(), GLFW_KEY_F3) == GLFW_PRESS ) {
-		cam_mode = cam_arcball;
-	}
-
-	if ( cam_mode == cam_arcball || input.mouseDown( fggl::gfx::MOUSE_2 ) ) {
-		CameraArcball(it, t, c);
-	} else if ( cam_mode == cam_free ) {
-		CameraFree(it, t, c);
-	}
-
-	// scroll wheel
-	auto& camTransform = t[0];
-	auto& camComp = c[0];
-
-	CameraEdgeScroll(input, camTransform, camComp);
-
-	const glm::vec3 dir = ( camTransform.origin() - camComp.target );
-	const glm::vec3 forward = glm::normalize( dir );
-
-	glm::vec3 motion(0.0f);
-	float delta = (float)input.scrollDeltaY();
-	if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) )
-		motion -= (forward * delta);
-
-	camTransform.origin( camTransform.origin() + motion );
-}
-
-void CameraArcball(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c) {
-	CameraHacks* hacks = (CameraHacks*)( it.ctx() );
-	const fggl::gfx::Window& window = hacks->window;
-	const fggl::gfx::Input& input = hacks->input;
-
-	auto& camTransform = t[0];
-	auto& camComp = c[0];
-
-	// see https://asliceofrendering.com/camera/2019/11/30/ArcballCamera/
-	glm::vec4 position(camTransform.origin(), 1.0f);
-	glm::vec4 pivot(camComp.target, 1.0f);
-	glm::mat4 view = glm::lookAt( camTransform.origin(), camComp.target, camTransform.up() );
-	glm::vec3 viewDir = -glm::transpose(view)[2];
-	glm::vec3 rightDir = glm::transpose(view)[0];
-
-	float deltaAngleX = ( 2 * M_PI / window.width() ); 
-	float deltaAngleY = ( M_PI / window.height() );
-	float xAngle = ( input.cursorDeltaX() * window.width() ) * deltaAngleX;
-	float yAngle = ( input.cursorDeltaY() * window.height() ) * deltaAngleY;
-
-	auto cosAngle = glm::dot( viewDir, fggl::math::UP );
-	if ( cosAngle * sgn(deltaAngleY) > 0.99f ) {
-		deltaAngleY = 0;
-	}
-
-	// rotate the camera around the pivot on the first axis
-	glm::mat4x4 rotationMatrixX(1.0f);
-	rotationMatrixX = glm::rotate( rotationMatrixX, xAngle, fggl::math::UP );
-	position = ( rotationMatrixX * ( position - pivot ) ) + pivot;
-
-	// rotate the camera aroud the pivot on the second axis
-	glm::mat4x4 rotationMatrixY(1.0f);
-	rotationMatrixY = glm::rotate(rotationMatrixY, yAngle, rightDir );
-	auto finalPos = ( rotationMatrixY * ( position - pivot ) ) + pivot;
-
-	camTransform.origin( finalPos );
-}
-
-constexpr float ROT_SPEED = 0.05f;
-constexpr float PAN_SPEED = 0.05f;
-constexpr glm::mat4 MAT_IDENTITY(1.0f);
-
-void CameraFree(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c) {
-	CameraHacks* hacks = (CameraHacks*)( it.ctx() );
-	fggl::gfx::Window& window = hacks->window;
-	fggl::gfx::Input& input = hacks->input;
-
-	auto& camTransform = t[0];
-	auto& camComp = c[0];
-
-	float rotationValue = 0.0f;
-	glm::vec3 translation(0.0f);
-
-	// calulate rotation (user input)
-	if ( glfwGetKey(window.handle(), GLFW_KEY_Q) == GLFW_PRESS ) {
-		rotationValue = ROT_SPEED; 
-	} else if ( glfwGetKey(window.handle(), GLFW_KEY_E) == GLFW_PRESS ) {
-		rotationValue = -ROT_SPEED;
-	}
-
-	// calulate movement (user input)
-	if ( glfwGetKey(window.handle(), GLFW_KEY_W) == GLFW_PRESS ) {
-		translation -= fggl::math::RIGHT;
-	}
-
-	if ( glfwGetKey(window.handle(), GLFW_KEY_S) == GLFW_PRESS ) {
-		translation += fggl::math::RIGHT;
-	}
-
-	if ( glfwGetKey(window.handle(), GLFW_KEY_D) == GLFW_PRESS ) {
-		translation += fggl::math::FORWARD;
-	}
-
-	if ( glfwGetKey(window.handle(), GLFW_KEY_A) == GLFW_PRESS ) {
-		translation -= fggl::math::FORWARD;
-	}
-
-	glm::vec4 position( camTransform.origin(), 1.0f );
-	glm::vec4 pivot( camComp.target, 1.0f );
-
-	// apply movement
-	if ( translation != glm::vec3(0.0f) ) {
-		const auto rotation = (position - pivot);
-		const float angle = atan2( rotation.x, rotation.z );
-		const auto rotationMat = glm::rotate( MAT_IDENTITY, angle, fggl::math::UP );
-
-		auto deltaMove = (rotationMat * glm::vec4( translation, 1.0f )) * PAN_SPEED;
-		deltaMove.w = 0.0f;
-
-		position += deltaMove;
-		pivot += deltaMove;
-	}
-
-	// apply rotation
-	if ( rotationValue != 0.0f ) {
-		glm::mat4 rotation = glm::rotate( MAT_IDENTITY, rotationValue, fggl::math::UP );
-		position = ( rotation * ( position - pivot ) ) + pivot;
-	}
-
-	camTransform.origin( position );
-	camComp.target = pivot;
-}
-
-void CameraEdgeScroll(fggl::gfx::Input& input, fggl::components::Transform& t, fggl::components::Camera& c) {
-	auto cursor = input.mousePos();
-
-	float rotationValue = 0.0f;
-	glm::vec3 translation(0.0f);
-
-	// calulate movement (user input)
-	if ( cursor[1] > 0.95f ) {
-		translation += fggl::math::RIGHT;
-	}
-
-	if ( cursor[1] < -0.95f ) {
-		translation -= fggl::math::RIGHT;
-	}
-
-	if ( cursor[0] > 0.95f ) {
-		translation += fggl::math::FORWARD;
-	}
-
-	if ( cursor[0] < -0.95f ) {
-		translation -= fggl::math::FORWARD;
-	}
-
-	glm::vec4 position( t.origin(), 1.0f );
-	glm::vec4 pivot( c.target, 1.0f );
-
-	// apply movement
-	if ( translation != glm::vec3(0.0f) ) {
-		const auto rotation = (position - pivot);
-		const float angle = atan2( rotation.x, rotation.z );
-		const auto rotationMat = glm::rotate( MAT_IDENTITY, angle, fggl::math::UP );
-
-		auto deltaMove = (rotationMat * glm::vec4( translation, 1.0f )) * PAN_SPEED;
-		deltaMove.w = 0.0f;
-
-		position += deltaMove;
-		pivot += deltaMove;
-	}
-
-	// apply rotation
-	if ( rotationValue != 0.0f ) {
-		glm::mat4 rotation = glm::rotate( MAT_IDENTITY, rotationValue, fggl::math::UP );
-		position = ( rotation * ( position - pivot ) ) + pivot;
-	}
-
-	t.origin( position );
-	c.target = pivot;
-}
-
 void loadShader(fggl::gfx::ShaderCache& cache, const char* name, bool hasGeom) {
 	fggl::gfx::ShaderConfig config;
 	config.name = name;
@@ -278,9 +80,6 @@ int main(int argc, char* argv[]) {
 	fggl::data::Storage storage;
 	discover( storage.resolvePath(fggl::data::Data, "res") );
 
-	// Hacks to make ECS work, should probably be fixed...
-	CameraHacks hacks{win, fggl::gfx::Input::instance()};
-
 	fggl::gfx::ShaderCache cache(storage);
 	loadShader(cache, "unlit", false);
 	loadShader(cache, "phong", false);
@@ -292,8 +91,12 @@ int main(int argc, char* argv[]) {
 	// load the modules
 	world.import<fggl::components::Math>();
 	world.import<fggl::components::Gfx>();
+	world.import<fggl::components::CameraMod>();
 
 	world.import<fggl::systems::OpenGL>();
+	world.import<fggl::systems::CameraInput>();
+
+	world.ecs().set<fggl::components::GfxHacks>({ &win });
 
 	// make camera
 	auto camEnt = world.create("camera");
@@ -311,9 +114,10 @@ int main(int argc, char* argv[]) {
 
 		fggl::data::Mesh mesh = fggl::data::make_quad_xz();
 		auto token = meshRenderer.upload( mesh );
-		floorEnt.set<fggl::components::GfxToken>( { } );
+		floorEnt.set<fggl::components::GfxToken>( token );
 	}
 
+	// bunker prefab
 	constexpr float HALF_PI = M_PI / 2.0f;
 	constexpr int nSections = 2;
 	auto bunkerPrototype = world.prefab("bunker");
@@ -361,10 +165,6 @@ int main(int argc, char* argv[]) {
 
 	fggl::gfx::Input& input = fggl::gfx::Input::instance();
 
-	world.ecs().system<fggl::components::Transform, fggl::components::Camera>()
-		.ctx( &hacks )
-		.iter( CameraInput );
-
 	bool joystickWindow = true;
 	bool gamepadWindow = true;
 
diff --git a/fggl/gfx/CMakeLists.txt b/fggl/gfx/CMakeLists.txt
index 961857887e88901d4ec059d044e0f45ce0aaa1d2..c64d93f8fe37ee9ba5bfd9ab562df4ae887aa40b 100644
--- a/fggl/gfx/CMakeLists.txt
+++ b/fggl/gfx/CMakeLists.txt
@@ -7,6 +7,7 @@ target_sources(fggl
 	input.cpp
 	shader.cpp
 	ogl.cpp
+	ecs-cam.cpp
 )
 
 # OpenGL Backend
diff --git a/fggl/gfx/ecs-cam.cpp b/fggl/gfx/ecs-cam.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1b7c995b0c631d2c03f879fea2715b9f4eecf3f3
--- /dev/null
+++ b/fggl/gfx/ecs-cam.cpp
@@ -0,0 +1,201 @@
+#include <fggl/gfx/ecs.hpp>
+#include <fggl/gfx/ecs-cam.hpp>
+
+#include <fggl/gfx/window.hpp>
+
+using fggl::math::sgn;
+
+namespace fggl::systems {
+
+	constexpr float ROT_SPEED = 0.05f;
+	constexpr float PAN_SPEED = 0.05f;
+	constexpr glm::mat4 MAT_IDENTITY(1.0f);
+
+	enum camera_type { cam_free, cam_arcball };
+	camera_type cam_mode = cam_free;
+
+	void ProcessCameraInput(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c) {
+		//FIXME only required because the imput system is not finished
+		fggl::gfx::Window* window = it.world().get<fggl::components::GfxHacks>()->window;
+		fggl::gfx::Input& input = fggl::gfx::Input::instance();
+
+		//TODO proper input system
+		if ( glfwGetKey(window->handle(), GLFW_KEY_F2) == GLFW_PRESS ) {
+			cam_mode = cam_free; 
+		}
+		if ( glfwGetKey(window->handle(), GLFW_KEY_F3) == GLFW_PRESS ) {
+			cam_mode = cam_arcball;
+		}
+
+		if ( cam_mode == cam_arcball || input.mouseDown( fggl::gfx::MOUSE_2 ) ) {
+			CameraArcball(it, t, c);
+		} else if ( cam_mode == cam_free ) {
+			CameraFree(it, t, c);
+		}
+
+		// scroll wheel
+		auto& camTransform = t[0];
+		auto& camComp = c[0];
+
+		CameraEdgeScroll(input, camTransform, camComp);
+
+		const glm::vec3 dir = ( camTransform.origin() - camComp.target );
+		const glm::vec3 forward = glm::normalize( dir );
+
+		glm::vec3 motion(0.0f);
+		float delta = (float)input.scrollDeltaY();
+		if ( (glm::length( dir ) < 25.0f && delta < 0.0f) || (glm::length( dir ) > 2.5f && delta > 0.0f) )
+			motion -= (forward * delta);
+
+		camTransform.origin( camTransform.origin() + motion );
+	}
+
+	void CameraArcball(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c) {
+		const fggl::gfx::Input& input = fggl::gfx::Input::instance();
+
+		auto& camTransform = t[0];
+		auto& camComp = c[0];
+
+		// see https://asliceofrendering.com/camera/2019/11/30/ArcballCamera/
+		glm::vec4 position(camTransform.origin(), 1.0f);
+		glm::vec4 pivot(camComp.target, 1.0f);
+		glm::mat4 view = glm::lookAt( camTransform.origin(), camComp.target, camTransform.up() );
+		glm::vec3 viewDir = -glm::transpose(view)[2];
+		glm::vec3 rightDir = glm::transpose(view)[0];
+
+		float deltaAngleX = ( 2 * M_PI ); 
+		float deltaAngleY = ( M_PI );
+		float xAngle = ( input.cursorDeltaX() ) * deltaAngleX;
+		float yAngle = ( input.cursorDeltaY() ) * deltaAngleY;
+
+		auto cosAngle = glm::dot( viewDir, fggl::math::UP );
+		if ( cosAngle * sgn(deltaAngleY) > 0.99f ) {
+			deltaAngleY = 0;
+		}
+
+		// rotate the camera around the pivot on the first axis
+		glm::mat4x4 rotationMatrixX(1.0f);
+		rotationMatrixX = glm::rotate( rotationMatrixX, xAngle, fggl::math::UP );
+		position = ( rotationMatrixX * ( position - pivot ) ) + pivot;
+
+		// rotate the camera aroud the pivot on the second axis
+		glm::mat4x4 rotationMatrixY(1.0f);
+		rotationMatrixY = glm::rotate(rotationMatrixY, yAngle, rightDir );
+		auto finalPos = ( rotationMatrixY * ( position - pivot ) ) + pivot;
+
+		camTransform.origin( finalPos );
+	}
+
+
+	void CameraFree(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c) {
+
+		//FIXME only required because the imput system is not finished
+		fggl::gfx::Window* window = it.world().get<fggl::components::GfxHacks>()->window;
+		const fggl::gfx::Input& input = fggl::gfx::Input::instance();
+
+		auto& camTransform = t[0];
+		auto& camComp = c[0];
+
+		float rotationValue = 0.0f;
+		glm::vec3 translation(0.0f);
+
+		// calulate rotation (user input)
+		if ( glfwGetKey(window->handle(), GLFW_KEY_Q) == GLFW_PRESS ) {
+			rotationValue = ROT_SPEED; 
+		} else if ( glfwGetKey(window->handle(), GLFW_KEY_E) == GLFW_PRESS ) {
+			rotationValue = -ROT_SPEED;
+		}
+
+		// calulate movement (user input)
+		if ( glfwGetKey(window->handle(), GLFW_KEY_W) == GLFW_PRESS ) {
+			translation -= fggl::math::RIGHT;
+		}
+
+		if ( glfwGetKey(window->handle(), GLFW_KEY_S) == GLFW_PRESS ) {
+			translation += fggl::math::RIGHT;
+		}
+
+		if ( glfwGetKey(window->handle(), GLFW_KEY_D) == GLFW_PRESS ) {
+			translation += fggl::math::FORWARD;
+		}
+
+		if ( glfwGetKey(window->handle(), GLFW_KEY_A) == GLFW_PRESS ) {
+			translation -= fggl::math::FORWARD;
+		}
+
+		glm::vec4 position( camTransform.origin(), 1.0f );
+		glm::vec4 pivot( camComp.target, 1.0f );
+
+		// apply movement
+		if ( translation != glm::vec3(0.0f) ) {
+			const auto rotation = (position - pivot);
+			const float angle = atan2( rotation.x, rotation.z );
+			const auto rotationMat = glm::rotate( MAT_IDENTITY, angle, fggl::math::UP );
+
+			auto deltaMove = (rotationMat * glm::vec4( translation, 1.0f )) * PAN_SPEED;
+			deltaMove.w = 0.0f;
+
+			position += deltaMove;
+			pivot += deltaMove;
+		}
+
+		// apply rotation
+		if ( rotationValue != 0.0f ) {
+			glm::mat4 rotation = glm::rotate( MAT_IDENTITY, rotationValue, fggl::math::UP );
+			position = ( rotation * ( position - pivot ) ) + pivot;
+		}
+
+		camTransform.origin( position );
+		camComp.target = pivot;
+	}
+
+	void CameraEdgeScroll(fggl::gfx::Input& input, fggl::components::Transform& t, fggl::components::Camera& c) {
+		auto cursor = input.mousePos();
+
+		float rotationValue = 0.0f;
+		glm::vec3 translation(0.0f);
+
+		// calulate movement (user input)
+		if ( cursor[1] > 0.95f ) {
+			translation += fggl::math::RIGHT;
+		}
+
+		if ( cursor[1] < -0.95f ) {
+			translation -= fggl::math::RIGHT;
+		}
+
+		if ( cursor[0] > 0.95f ) {
+			translation += fggl::math::FORWARD;
+		}
+
+		if ( cursor[0] < -0.95f ) {
+			translation -= fggl::math::FORWARD;
+		}
+
+		glm::vec4 position( t.origin(), 1.0f );
+		glm::vec4 pivot( c.target, 1.0f );
+
+		// apply movement
+		if ( translation != glm::vec3(0.0f) ) {
+			const auto rotation = (position - pivot);
+			const float angle = atan2( rotation.x, rotation.z );
+			const auto rotationMat = glm::rotate( MAT_IDENTITY, angle, fggl::math::UP );
+
+			auto deltaMove = (rotationMat * glm::vec4( translation, 1.0f )) * PAN_SPEED;
+			deltaMove.w = 0.0f;
+
+			position += deltaMove;
+			pivot += deltaMove;
+		}
+
+		// apply rotation
+		if ( rotationValue != 0.0f ) {
+			glm::mat4 rotation = glm::rotate( MAT_IDENTITY, rotationValue, fggl::math::UP );
+			position = ( rotation * ( position - pivot ) ) + pivot;
+		}
+
+		t.origin( position );
+		c.target = pivot;
+	}
+
+};
diff --git a/fggl/gfx/ecs-cam.hpp b/fggl/gfx/ecs-cam.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..19e8b717ced4a379e7d6ab92f10319d6bbe56847
--- /dev/null
+++ b/fggl/gfx/ecs-cam.hpp
@@ -0,0 +1,48 @@
+#ifndef FGGL_GFX_ECS_CAM_H
+#define FGGL_GFX_ECS_CAM_H
+
+#include <fggl/ecs2/ecs.hpp>
+#include <fggl/math/types.hpp>
+#include <fggl/math/ecs.hpp>
+
+#include <fggl/gfx/input.hpp>
+
+namespace fggl::components {
+
+	struct Camera {
+		math::vec3 target = math::vec3(0.0f, 0.0f, 0.0f);
+		float aspectRatio = 1280.0f / 720.0f;
+		float fov = glm::radians(45.0f);
+		float nearPlane = 0.1f;
+		float farPlane = 100.0f;
+	};
+
+	class CameraMod {
+		public:
+			inline CameraMod(ecs2::impl::world& world) {
+				world.module<CameraMod>("cam");
+				world.component<Camera>("camera");
+			}
+	};
+};
+
+namespace fggl::systems {
+
+	void CameraArcball(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c);
+	void CameraFree(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c);
+	void CameraEdgeScroll(fggl::gfx::Input& input, fggl::components::Transform& t, fggl::components::Camera& c);
+	void ProcessCameraInput(fggl::ecs2::impl::iter& it, fggl::components::Transform* t, fggl::components::Camera* c);
+
+	class CameraInput {
+		public:
+			inline CameraInput(ecs2::impl::world& world) {
+				world.module<CameraInput>("CamInput");
+
+				world.system<fggl::components::Transform, fggl::components::Camera>()
+					.iter( ProcessCameraInput );
+			}
+	};
+
+}
+
+#endif
diff --git a/fggl/gfx/ecs.hpp b/fggl/gfx/ecs.hpp
index 6201de7ab076f22186983baaf8f117a313117d07..33d2b94eb11f439447eb757774fea8f45d1828c1 100644
--- a/fggl/gfx/ecs.hpp
+++ b/fggl/gfx/ecs.hpp
@@ -1,6 +1,7 @@
 #ifndef FGGL_GFX_ECS_H
 #define FGGL_GFX_ECS_H
 
+#include "window.hpp"
 #include <fggl/ecs2/ecs.hpp>
 #include <fggl/math/types.hpp>
 
@@ -23,12 +24,9 @@ namespace fggl::components {
 		math::mat4 proj;
 	};
 
-	struct Camera {
-		math::vec3 target = math::vec3(0.0f, 0.0f, 0.0f);
-		float aspectRatio = 1280.0f / 720.0f;
-		float fov = glm::radians(45.0f);
-		float nearPlane = 0.1f;
-		float farPlane = 100.0f;
+	// FIXME imperfect abstraction - needs fixing
+	struct GfxHacks {
+		fggl::gfx::Window* window;
 	};
 
 	class Gfx {
@@ -36,7 +34,6 @@ namespace fggl::components {
 			inline Gfx(ecs2::impl::world& world) {
 				world.module<Gfx>("gfx");
 				world.component<GfxToken>("token");
-				world.component<Camera>("camera");
 				world.component<GfxMat>("mat");
 			}
 	};
diff --git a/fggl/gfx/renderer.cpp b/fggl/gfx/renderer.cpp
index 4f1b3c4208b496623b756c2acd1690be8be08bab..d1b1fdb4a7298d7b3f56eca8455c0c028b6af826 100644
--- a/fggl/gfx/renderer.cpp
+++ b/fggl/gfx/renderer.cpp
@@ -4,6 +4,7 @@
 
 #include <fggl/math/ecs.hpp>
 #include <fggl/gfx/ecs.hpp>
+#include <fggl/gfx/ecs-cam.hpp>
 
 #include <fggl/data/model.hpp>
 
diff --git a/fggl/math/types.hpp b/fggl/math/types.hpp
index e4b3c4612651aef354fd78c2446cc701cdbf2a15..bb201f22e591cc7c82d6b0512155dfc12311a872 100644
--- a/fggl/math/types.hpp
+++ b/fggl/math/types.hpp
@@ -23,6 +23,10 @@ namespace fggl::math {
 	constexpr vec3 FORWARD { 1.0f, 0.0f, 0.0f };
 	constexpr vec3 RIGHT { 0.0f, 0.0f, 1.0f };
 
+	template <typename T> int sgn(T val) {
+		return (T(0) < val) - (val < T(0));
+	}
+
 	inline glm::mat4 modelMatrix( const vec3 offset, const quat rotation ) {
 		return glm::translate( glm::mat4(1.0f), offset ) * glm::toMat4( rotation );
 	}