diff --git a/config/Sources.cmake b/config/Sources.cmake
index 6cf9c6f4663c4307ee6c9350941cbe6e2f2b5a2c..1b3af31e82a5475d7d0791848fee123d62bf917c 100644
--- a/config/Sources.cmake
+++ b/config/Sources.cmake
@@ -54,6 +54,8 @@ set(vkcv_sources
 		${vkcv_include}/vkcv/PipelineConfig.hpp
 		${vkcv_source}/vkcv/PipelineConfig.cpp
 		
+		${vkcv_include}/vkcv/Interpolation.hpp
+		
 		${vkcv_include}/vkcv/Logger.hpp
 		
 		${vkcv_include}/vkcv/ShaderStage.hpp
diff --git a/include/vkcv/Interpolation.hpp b/include/vkcv/Interpolation.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..c38a7aa746e36a3a58a0c3dd1c94db549ba101e7
--- /dev/null
+++ b/include/vkcv/Interpolation.hpp
@@ -0,0 +1,121 @@
+#pragma once
+/**
+ * @authors Tobias Frisch
+ * @file vkcv/Interpolation.hpp
+ * @brief Structure for interpolation
+ */
+ 
+#include <algorithm>
+#include <cmath>
+#include <functional>
+#include <vector>
+
+namespace vkcv {
+	
+	template<typename V, typename T = double>
+	struct interpolation_function {
+		typedef std::function<V(const V&, const V&, T)> type;
+	};
+	
+	template<typename V, typename T = double>
+	struct interpolation_state {
+		T time;
+		V value;
+		
+		bool operator<(const interpolation_state& other) const {
+			return time < other.time;
+		}
+	};
+	
+	template<typename V, typename T = double>
+	struct interpolation {
+	private:
+		typename interpolation_function<V, T>::type m_function;
+		std::vector< interpolation_state<V, T> > m_states;
+	
+	public:
+		interpolation(const typename interpolation_function<V, T>::type& function)
+		: m_function(function), m_states() {}
+		
+		interpolation(const interpolation& other) = default;
+		interpolation(interpolation&& other) noexcept = default;
+		
+		~interpolation() = default;
+		
+		interpolation& operator=(const interpolation& other) = default;
+		interpolation& operator=(interpolation&& other) noexcept = default;
+		
+		void clear() {
+			m_states.clear();
+		}
+		
+		void add(T time, const V& value) {
+			interpolation_state<V, T> state;
+			state.time = time;
+			state.value = value;
+			m_states.insert(
+					std::lower_bound(m_states.begin(), m_states.end(), state),
+					state
+			);
+		}
+		
+		V operator()(T time) const {
+			interpolation_state<V, T> state;
+			state.time = time;
+			
+			auto end = std::lower_bound(m_states.begin(), m_states.end(), state);
+			auto start = end != m_states.begin()? (end - 1) : m_states.begin();
+			
+			if (end == m_states.end()) {
+				end = start;
+			}
+			
+			const T ratio = (time - (*start).time) / ((*end).time - (*start).time);
+			
+			return m_function(
+					(*start).value,
+					(*end).value,
+					std::clamp<T>(
+							ratio,
+							static_cast<T>(0),
+							static_cast<T>(1)
+					)
+			);
+		}
+	
+	};
+	
+	template<typename V, typename T = double>
+	interpolation<V, T> linearInterpolation() {
+		return interpolation<V, T>([](const V& start, const V& end, T ratio) {
+			return start * (static_cast<T>(1) - ratio) + end * ratio;
+		});
+	}
+	
+	template<typename V, typename T = double>
+	interpolation<V, T> cubicInterpolation() {
+		return interpolation<V, T>([](const V& start, const V& end, T ratio) {
+			const T r0 = (static_cast<T>(1) - ratio) * (static_cast<T>(1) - ratio);
+			const T r1 = ratio * ratio;
+			
+			return (
+				start * r0 +
+				start * r0 * ratio * static_cast<T>(2) +
+				end * r1 * (static_cast<T>(1) - ratio) * static_cast<T>(2) +
+				end * r1
+			);
+		});
+	}
+	
+	template<typename V, typename T = double>
+	interpolation<V, T> cosInterpolation() {
+		return interpolation<V, T>([](const V& start, const V& end, T ratio) {
+			const T cos_ratio = (static_cast<T>(1) - std::cos(
+					ratio * static_cast<T>(M_PI)
+			)) / static_cast<T>(2);
+			
+			return start * (static_cast<T>(1) - cos_ratio) + end * cos_ratio;
+		});
+	}
+	
+}
\ No newline at end of file
diff --git a/modules/camera/include/vkcv/camera/CameraController.hpp b/modules/camera/include/vkcv/camera/CameraController.hpp
index d6efd8f2f300dead870b3546939dad2088cd466e..0b5a17faf178a01f6e1460fb5b86bff901576c68 100644
--- a/modules/camera/include/vkcv/camera/CameraController.hpp
+++ b/modules/camera/include/vkcv/camera/CameraController.hpp
@@ -22,7 +22,6 @@ namespace vkcv::camera {
      * PilotCameraController.
      */
     class CameraController {
-
     public:
 
         /**
diff --git a/modules/camera/include/vkcv/camera/CameraManager.hpp b/modules/camera/include/vkcv/camera/CameraManager.hpp
index 3c6c556d96210287e3be23d19346c5eca6f9c1cd..01b11eedcffcbbf588fa93c48f4762e4f4adbdc6 100644
--- a/modules/camera/include/vkcv/camera/CameraManager.hpp
+++ b/modules/camera/include/vkcv/camera/CameraManager.hpp
@@ -108,12 +108,6 @@ namespace vkcv::camera {
 		 * @return The specified camera controller object.
 		 */
 		CameraController& getControllerByType(ControllerType controllerType);
-        
-        /**
-         * @briof A method to get the currently active controller for the active camera.
-         * @return Reference to the active #CameraController
-         */
-        CameraController& getActiveController();
 
     public:
 
@@ -152,12 +146,14 @@ namespace vkcv::camera {
          * @return The camera object by @p cameraHandle.
          * @throws std::runtime_error If @p cameraHandle is not a valid camera handle.
          */
+		[[nodiscard]]
         Camera& getCamera(const CameraHandle& cameraHandle);
 
         /**
          * @brief Returns the stored camera object set as the active camera.
          * @return The active camera.
          */
+		[[nodiscard]]
         Camera& getActiveCamera();
 
         /**
@@ -171,6 +167,7 @@ namespace vkcv::camera {
          * @brief Returns the handle of the stored active camera object.
          * @return The active camera handle.
          */
+		[[nodiscard]]
 		CameraHandle getActiveCameraHandle() const;
 
         /**
@@ -188,6 +185,7 @@ namespace vkcv::camera {
          * @return The type of the camera controller of the specified camera object.
          * @throws std::runtime_error If @p cameraHandle is not a valid camera handle.
          */
+		[[nodiscard]]
         ControllerType getControllerType(const CameraHandle& cameraHandle);
 
         /**
diff --git a/modules/camera/include/vkcv/camera/ControllerType.hpp b/modules/camera/include/vkcv/camera/ControllerType.hpp
index c1a5ef34bf8172956d5c9812f45b73f746c48838..3e85c1f9749ced9680030e95d08a1c6bc39aafb3 100644
--- a/modules/camera/include/vkcv/camera/ControllerType.hpp
+++ b/modules/camera/include/vkcv/camera/ControllerType.hpp
@@ -19,8 +19,7 @@ namespace vkcv::camera {
 	enum class ControllerType {
 		NONE,
 		PILOT,
-		TRACKBALL,
-		TRACE
+		TRACKBALL
 	};
 	
 	/** @} */
diff --git a/modules/camera/src/vkcv/camera/CameraManager.cpp b/modules/camera/src/vkcv/camera/CameraManager.cpp
index a2ead14ce30d8088ea77ecbfd20d5af29c2c181d..ad5de753a17e7f207cd3e538cb3b96a6c2753083 100644
--- a/modules/camera/src/vkcv/camera/CameraManager.cpp
+++ b/modules/camera/src/vkcv/camera/CameraManager.cpp
@@ -41,53 +41,78 @@ namespace vkcv::camera {
         }
     }
 
-    void CameraManager::mouseButtonCallback(int button, int action, int mods){
-        if(button == GLFW_MOUSE_BUTTON_2 && action == GLFW_PRESS){
+    void CameraManager::mouseButtonCallback(int button, int action, int mods) {
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+		
+        if ((button == GLFW_MOUSE_BUTTON_2) && (action == GLFW_PRESS)) {
             glfwSetInputMode(m_window.getWindow(), GLFW_CURSOR, GLFW_CURSOR_DISABLED);
-        }
-        else if(button == GLFW_MOUSE_BUTTON_2 && action == GLFW_RELEASE){
+        } else
+		if ((button == GLFW_MOUSE_BUTTON_2) && (action == GLFW_RELEASE)) {
             glfwSetInputMode(m_window.getWindow(), GLFW_CURSOR, GLFW_CURSOR_NORMAL);
         }
-		getActiveController().mouseButtonCallback(button, action, mods, getActiveCamera());
+	
+		if (type == ControllerType::NONE) {
+			return;
+		}
+		
+		getControllerByType(type).mouseButtonCallback(button, action, mods, getActiveCamera());
     }
 
-    void CameraManager::mouseMoveCallback(double x, double y){
+    void CameraManager::mouseMoveCallback(double x, double y) {
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+		
         auto xoffset = static_cast<float>(x - m_lastX) / m_window.getWidth();
 		auto yoffset = static_cast<float>(y - m_lastY) / m_window.getHeight();
         m_lastX = x;
         m_lastY = y;
-		getActiveController().mouseMoveCallback(xoffset, yoffset, getActiveCamera());
+	
+		if (type == ControllerType::NONE) {
+			return;
+		}
+		
+		getControllerByType(type).mouseMoveCallback(xoffset, yoffset, getActiveCamera());
     }
 
     void CameraManager::scrollCallback(double offsetX, double offsetY) {
-		getActiveController().scrollCallback(offsetX, offsetY, getActiveCamera());
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+	
+		if (type == ControllerType::NONE) {
+			return;
+		}
+		
+		getControllerByType(type).scrollCallback(offsetX, offsetY, getActiveCamera());
     }
 
     void CameraManager::keyCallback(int key, int scancode, int action, int mods)  {
-        switch (action) {
-            case GLFW_RELEASE:
-                switch (key) {
-                    case GLFW_KEY_TAB:
-                        if (m_activeCameraIndex + 1 == m_cameras.size()) {
-                            m_activeCameraIndex = 0;
-                        }
-                        else {
-                            m_activeCameraIndex++;
-                        }
-                        return;
-                    case GLFW_KEY_ESCAPE:
-                        glfwSetWindowShouldClose(m_window.getWindow(), 1);
-                        return;
-					default:
-						break;
-                }
-            default:
-				getActiveController().keyCallback(key, scancode, action, mods, getActiveCamera());
-                break;
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+		
+        if (action == GLFW_RELEASE) {
+			switch (key) {
+				case GLFW_KEY_TAB:
+					if (m_activeCameraIndex + 1 == m_cameras.size()) {
+						m_activeCameraIndex = 0;
+					} else {
+						m_activeCameraIndex++;
+					}
+					return;
+				case GLFW_KEY_ESCAPE:
+					glfwSetWindowShouldClose(m_window.getWindow(), 1);
+					return;
+				default:
+					break;
+			}
         }
+		
+		if (type == ControllerType::NONE) {
+			return;
+		}
+	
+		getControllerByType(type).keyCallback(key, scancode, action, mods, getActiveCamera());
     }
 
     void CameraManager::gamepadCallback(int gamepadIndex) {
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+		
         // handle camera switching
         GLFWgamepadstate gamepadState;
         glfwGetGamepadState(gamepadIndex, &gamepadState);
@@ -96,22 +121,23 @@ namespace vkcv::camera {
         if (time - m_inputDelayTimer > 0.2) {
             int switchDirection = gamepadState.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT] - gamepadState.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT];
             m_activeCameraIndex += switchDirection;
-            if (std::greater<int>{}(m_activeCameraIndex, m_cameras.size() - 1)) {
+            
+			if (std::greater<int>{}(m_activeCameraIndex, m_cameras.size() - 1)) {
                 m_activeCameraIndex = 0;
-            }
-            else if (std::less<int>{}(m_activeCameraIndex, 0)) {
+            } else
+			if (std::less<int>{}(m_activeCameraIndex, 0)) {
                 m_activeCameraIndex = m_cameras.size() - 1;
             }
+			
             uint32_t triggered = abs(switchDirection);
             m_inputDelayTimer = (1-triggered)*m_inputDelayTimer + triggered * time; // Only reset timer, if dpad was pressed - is this cheaper than if-clause?
         }
-
-        getActiveController().gamepadCallback(gamepadIndex, getActiveCamera(), m_frameTime);     // handle camera rotation, translation
-    }
-
-    CameraController& CameraManager::getActiveController() {
-    	const ControllerType type = getControllerType(getActiveCameraHandle());
-    	return getControllerByType(type);
+		
+		if (type == ControllerType::NONE) {
+			return;
+		}
+	
+		getControllerByType(type).gamepadCallback(gamepadIndex, getActiveCamera(), m_frameTime);     // handle camera rotation, translation
     }
 	
 	CameraHandle CameraManager::addCamera(ControllerType controllerType) {
@@ -185,9 +211,17 @@ namespace vkcv::camera {
     }
 
     void CameraManager::update(double deltaTime) {
-        m_frameTime = deltaTime;
+		const ControllerType type = getControllerType(getActiveCameraHandle());
+		
+		if (type != ControllerType::NONE) {
+			m_frameTime = deltaTime;
+		} else {
+			m_frameTime = 0.0;
+			return;
+		}
+		
         if (glfwGetWindowAttrib(m_window.getWindow(), GLFW_FOCUSED) == GLFW_TRUE) {
-            getActiveController().updateCamera(deltaTime, getActiveCamera());
+			getControllerByType(type).updateCamera(m_frameTime, getActiveCamera());
         }
 	}
 	
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index 2705c61cdb8755136d7c660aeb4a5e494b35a55e..0070b51eb51f81422af49b641a860f75ec832b3f 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -1,9 +1,11 @@
 #include <iostream>
 #include <vkcv/Core.hpp>
 #include <vkcv/Image.hpp>
+#include <vkcv/Interpolation.hpp>
 #include <vkcv/Pass.hpp>
 #include <vkcv/Sampler.hpp>
 #include <vkcv/camera/CameraManager.hpp>
+
 #include <vkcv/asset/asset_loader.hpp>
 #include <vkcv/shader/GLSLCompiler.hpp>
 
@@ -104,9 +106,22 @@ int main(int argc, const char** argv) {
 	drawcall.useDescriptorSet(0, descriptorSet);
 
     vkcv::camera::CameraManager cameraManager(window);
-    auto camHandle = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
 	
-	cameraManager.getCamera(camHandle).setPosition(glm::vec3(0, 0, -3));
+    auto camHandle0 = cameraManager.addCamera(vkcv::camera::ControllerType::NONE);
+	auto camHandle1 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
+	auto camHandle2 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
+	
+	cameraManager.getCamera(camHandle1).setPosition(glm::vec3(0, 0, -3));
+	cameraManager.getCamera(camHandle2).setPosition(glm::vec3(0, 0, -3));
+	
+	auto interp = vkcv::linearInterpolation<glm::vec3, float>();
+	
+	interp.add( 0.0f, glm::vec3(+5, +5, -5));
+	interp.add( 2.0f, glm::vec3(+0, +5, -5));
+	interp.add( 4.0f, glm::vec3(+0, -3, -3));
+	interp.add( 6.0f, glm::vec3(+3, +0, -6));
+	interp.add( 8.0f, glm::vec3(+5, +5, +5));
+	interp.add(10.0f, glm::vec3(+5, +5, -5));
 	
 	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
 				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
@@ -123,6 +138,10 @@ int main(int argc, const char** argv) {
 		}
 		
 		cameraManager.update(dt);
+		cameraManager.getCamera(camHandle0).setPosition(
+				interp(static_cast<float>(std::fmod<double>(t, 10.0)))
+		);
+		
         glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
 
 		vkcv::PushConstants pushConstants = vkcv::pushConstants<glm::mat4>();