diff --git a/build.sh b/build.sh
index b9b5e312a2663cd087eed9732ee64b858cdf6501..2bfc10fe749e1115fb4f58463516c1dcda3b08b0 100755
--- a/build.sh
+++ b/build.sh
@@ -1,5 +1,4 @@
 #! /bin/bash
-set -e
 
 if [[ ! -d "build/" ]]
 then
@@ -15,5 +14,5 @@ make
 popd
 
 pushd demo
-gdb ../build/demo/FgglDemo
+../build/demo/FgglDemo > /tmp/fggl.log 2>&1 &
 popd
diff --git a/demo/main.cpp b/demo/main.cpp
index 9e76acfad721f43e5685e79c62dc1cc9baa702a9..07cb316a8d693122d1903cd0676ac795d802b9c6 100644
--- a/demo/main.cpp
+++ b/demo/main.cpp
@@ -1,4 +1,5 @@
 #include <filesystem>
+#include <glm/ext/matrix_transform.hpp>
 #include <glm/geometric.hpp>
 #include <glm/trigonometric.hpp>
 #include <iostream>
@@ -45,28 +46,27 @@ void discover(std::filesystem::path base) {
 
 }
 
+enum camera_type { cam_free, cam_arcball };
+camera_type cam_mode = cam_free;
+
 //TODO proper input system
 void process_camera(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::Input& input, fggl::ecs::entity_t cam) {
-	auto camTransform = ecs.getComponent<fggl::math::Transform>(cam);
-	auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam);
-	float moveSpeed = 1.0f;
 
-	glm::vec3 dir = ( camTransform->origin() - camComp->target );
-	glm::vec3 forward = glm::normalize( dir );
-
-	glm::vec3 motion(0.0f);
-	if ( glfwGetKey(window.handle(), GLFW_KEY_W) == GLFW_PRESS ) {
-		if ( glm::length( dir ) > 2.5f ) {
-		  motion -= (forward * moveSpeed);
-		}
+	if ( glfwGetKey(window.handle(), GLFW_KEY_F2) == GLFW_PRESS ) {
+		cam_mode = cam_free; 
 	}
-	if ( glfwGetKey(window.handle(), GLFW_KEY_S) == GLFW_PRESS ) {
-		if ( glm::length( dir ) < 25.0f ) {
-		  motion += (forward * moveSpeed);
-		}
+	if ( glfwGetKey(window.handle(), GLFW_KEY_F3) == GLFW_PRESS ) {
+		cam_mode = cam_arcball;
 	}
 
+	auto camTransform = ecs.getComponent<fggl::math::Transform>(cam);
+	auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam);
+
+	const glm::vec3 dir = ( camTransform->origin() - camComp->target );
+	const glm::vec3 forward = glm::normalize( dir );
+
 	// scroll wheel
+	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);
@@ -108,6 +108,68 @@ void process_arcball(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::
 	camTransform->origin( finalPos );
 }
 
+constexpr float ROT_SPEED = 0.05f;
+constexpr float PAN_SPEED = 0.05f;
+constexpr glm::mat4 MAT_IDENTITY(1.0f);
+
+void process_freecam(fggl::gfx::Window& window, fggl::ecs::ECS& ecs, fggl::gfx::Input& input, fggl::ecs::entity_t cam) {
+	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;
+	}
+
+	// apply rotation/movement
+	auto camTransform = ecs.getComponent<fggl::math::Transform>(cam);
+	auto camComp = ecs.getComponent<fggl::gfx::Camera>(cam);
+
+	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;
+}
+
 int main(int argc, char* argv[]) {
 	fggl::gfx::Context ctx;
 
@@ -228,8 +290,12 @@ int main(int argc, char* argv[]) {
 		time += dt;
 
 		process_camera(win, ecs, input, camEnt);
-		if ( input.mouseDown( fggl::gfx::MOUSE_2 ) ) {
-			process_arcball(win, ecs, input, camEnt);
+		if ( cam_mode == cam_arcball ) { 
+			if ( input.mouseDown( fggl::gfx::MOUSE_2 ) ) {
+				process_arcball(win, ecs, input, camEnt);
+			}
+		} else if ( cam_mode == cam_free ) {
+			process_freecam(win, ecs, input, camEnt);
 		}
 
 		// imgui joystick debug