diff --git a/components/streams/CMakeLists.txt b/components/streams/CMakeLists.txt
index 877940ec26dfbfede03c664933dace46c21a23ee..7bb5dc6ac6dda4b07fa9bfa13eed1a3fb56e0379 100644
--- a/components/streams/CMakeLists.txt
+++ b/components/streams/CMakeLists.txt
@@ -18,6 +18,7 @@ set(STREAMSRC
 	src/renderer.cpp
 	src/renderers/screen_render.cpp
 	src/renderers/openvr_render.cpp
+	src/renderers/collisions.cpp
 )
 
 add_library(ftlstreams ${STREAMSRC})
diff --git a/components/streams/src/renderers/collisions.cpp b/components/streams/src/renderers/collisions.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..ca4262e750f15eef3dbff9a305595b31f8681a24
--- /dev/null
+++ b/components/streams/src/renderers/collisions.cpp
@@ -0,0 +1,67 @@
+#include "collisions.hpp"
+#include <ftl/codecs/touch.hpp>
+#include <ftl/utility/matrix_conversion.hpp>
+#include <ftl/rgbd/capabilities.hpp>
+#include <ftl/algorithms/dbscan.hpp>
+
+using ftl::codecs::Channel;
+using ftl::rgbd::Capability;
+
+void ftl::render::collision2touch(const ftl::rgbd::Frame &rgbdframe, const std::vector<float4> &collisions, const std::list<ftl::data::FrameSetPtr> &sets, uint32_t myid) {
+	std::vector<float4> clusters;
+	std::vector<short> labels;
+	ftl::dbscan<float4>(collisions, [](const std::vector<float4> &pts, size_t idx, float radius) {
+		std::vector<size_t> neighbors;
+		for (auto i = 0u; i < pts.size(); i++) {
+			if (i == idx) {
+				continue;
+			}
+			float dx = pts[idx].x - pts[i].x;
+			float dy = pts[idx].y - pts[i].y;
+
+			if (dx*dx+dy*dy < radius*radius) {
+				neighbors.push_back(i);
+			}
+		}
+		return neighbors;
+	}, 5, 16.0f, labels, clusters);
+
+	// TODO: Support multi-touch
+	if (clusters.size() == 1) {
+		//LOG(INFO) << "Found " << clusters.size() << " collisions";
+		//LOG(INFO) << "  -- " << clusters[0].x << "," << clusters[0].y << " " << clusters[0].z;
+
+		// Find all frames that support touch
+		for (auto &s : sets) {
+			if (s->frameset() == myid) continue;
+
+			for (const auto &f : s->frames) {
+				if (f.has(Channel::Capabilities)) {
+					const auto &cap = f.get<std::unordered_set<Capability>>(Channel::Capabilities);
+
+					// If it supports touch, calculate the touch points in screen coordinates
+					if (cap.count(Capability::TOUCH)){
+						const auto &rgbdf = f.cast<ftl::rgbd::Frame>();
+						auto pose = MatrixConversion::toCUDA((rgbdf.getPose().inverse() * rgbdframe.getPose()).cast<float>());
+						float3 campos = pose * rgbdframe.getLeft().screenToCam(clusters[0].x, clusters[0].y, clusters[0].z);
+						int2 pt = rgbdf.getLeft().camToScreen<int2>(campos);
+						//LOG(INFO) << "TOUCH AT " << pt.x << "," << pt.y << " - " << campos.z;
+
+						{
+							// Send the touch data
+							auto response = f.response();
+							auto &touches = response.create<std::vector<ftl::codecs::Touch>>(Channel::Touch);
+							auto &touch = touches.emplace_back();
+							touch.id = 0;
+							touch.x = pt.x;
+							touch.y = pt.y;
+							touch.type = ftl::codecs::TouchType::COLLISION;
+							touch.strength = 255;
+							touch.d = campos.z;
+						}
+					}
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/components/streams/src/renderers/collisions.hpp b/components/streams/src/renderers/collisions.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..a605759b8ab008cc6d9dc6865c0ec29b5469bfab
--- /dev/null
+++ b/components/streams/src/renderers/collisions.hpp
@@ -0,0 +1,15 @@
+#ifndef _FTL_RENDER_COLLISIONS_HPP_
+#define _FTL_RENDER_COLLISIONS_HPP_
+
+#include <ftl/data/new_frameset.hpp>
+#include <ftl/rgbd/frame.hpp>
+
+namespace ftl {
+namespace render {
+
+void collision2touch(const ftl::rgbd::Frame &rgbdframe, const std::vector<float4> &collisions, const std::list<ftl::data::FrameSetPtr> &sets, uint32_t myid);
+
+}
+}
+
+#endif
\ No newline at end of file
diff --git a/components/streams/src/renderers/screen_render.cpp b/components/streams/src/renderers/screen_render.cpp
index 580ebb84a1a67a15c774300b0dd6aa8f975c93db..a18b92c9f8af4ea5158a9555728f0b158a28108b 100644
--- a/components/streams/src/renderers/screen_render.cpp
+++ b/components/streams/src/renderers/screen_render.cpp
@@ -4,13 +4,12 @@
 #include <ftl/rgbd/capabilities.hpp>
 #include <ftl/operators/antialiasing.hpp>
 #include <ftl/operators/gt_analysis.hpp>
-#include <ftl/algorithms/dbscan.hpp>
 #include <ftl/utility/matrix_conversion.hpp>
-#include <ftl/codecs/touch.hpp>
 
 #include <loguru.hpp>
 
 #include "screen_render.hpp"
+#include "collisions.hpp"
 
 using ftl::render::Source;
 using ftl::render::ScreenRender;
@@ -139,65 +138,9 @@ bool ScreenRender::retrieve(ftl::data::Frame &frame_out) {
 
 		post_pipe_->apply(rgbdframe, rgbdframe, 0);
 
-
-		const auto &collisions = renderer_->getCollisions();
-		std::vector<float4> clusters;
-		std::vector<short> labels;
-		ftl::dbscan<float4>(collisions, [](const std::vector<float4> &pts, size_t idx, float radius) {
-			std::vector<size_t> neighbors;
-			for (auto i = 0u; i < pts.size(); i++) {
-				if (i == idx) {
-					continue;
-				}
-				float dx = pts[idx].x - pts[i].x;
-				float dy = pts[idx].y - pts[i].y;
-
-				if (dx*dx+dy*dy < radius*radius) {
-					neighbors.push_back(i);
-				}
-			}
-			return neighbors;
-		}, 5, 16.0f, labels, clusters);
-
-		// TODO: Support multi-touch
-		if (clusters.size() == 1) {
-			//LOG(INFO) << "Found " << clusters.size() << " collisions";
-			//LOG(INFO) << "  -- " << clusters[0].x << "," << clusters[0].y << " " << clusters[0].z;
-
-			// Find all frames that support touch
-			for (auto &s : sets) {
-				if (s->frameset() == my_id_) continue;
-
-				for (const auto &f : s->frames) {
-					if (f.has(Channel::Capabilities)) {
-						const auto &cap = f.get<std::unordered_set<Capability>>(Channel::Capabilities);
-
-						// If it supports touch, calculate the touch points
-						if (cap.count(Capability::TOUCH)){
-							const auto &rgbdf = f.cast<ftl::rgbd::Frame>();
-							auto pose = MatrixConversion::toCUDA((rgbdf.getPose().inverse() * rgbdframe.getPose()).cast<float>());
-							float3 campos = pose * rgbdframe.getLeft().screenToCam(clusters[0].x, clusters[0].y, clusters[0].z);
-							int2 pt = rgbdf.getLeft().camToScreen<int2>(campos);
-							//LOG(INFO) << "TOUCH AT " << pt.x << "," << pt.y << " - " << campos.z;
-
-							{
-								// Send the touch data
-								auto response = f.response();
-								auto &touches = response.create<std::vector<ftl::codecs::Touch>>(Channel::Touch);
-								auto &touch = touches.emplace_back();
-								touch.id = 0;
-								touch.x = pt.x;
-								touch.y = pt.y;
-								touch.type = ftl::codecs::TouchType::COLLISION;
-								touch.strength = 255;
-								touch.d = campos.z;
-							}
-						}
-					}
-				}
-			}
+		if (host_->value("enable_touch", false)) {
+			ftl::render::collision2touch(rgbdframe, renderer_->getCollisions(), sets, my_id_);
 		}
-
 		return true;
 	} else {
 		//LOG(INFO) << "Render fail";