diff --git a/src/components/cursor-controller.js b/src/components/cursor-controller.js
index 70b0b823b65d272644f73b8dbd1b37af35030c4f..35b72d9b51fd367384ed6ba17917e52bc43f709a 100644
--- a/src/components/cursor-controller.js
+++ b/src/components/cursor-controller.js
@@ -41,11 +41,10 @@ AFRAME.registerComponent("cursor-controller", {
 
     this.data.cursor.setAttribute("material", { color: this.data.cursorColorUnhovered });
 
-    this.startInteractionAndForceCursorUpdate = this.startInteractionAndForceCursorUpdate.bind(this);
+    this.forceCursorUpdate = this.forceCursorUpdate.bind(this);
     this.startInteraction = this.startInteraction.bind(this);
     this.moveCursor = this.moveCursor.bind(this);
     this.endInteraction = this.endInteraction.bind(this);
-    this.handleMouseWheel = this.handleMouseWheel.bind(this);
     this.changeDistanceMod = this.changeDistanceMod.bind(this);
 
     this._handleEnterVR = this._handleEnterVR.bind(this);
@@ -195,33 +194,29 @@ AFRAME.registerComponent("cursor-controller", {
     this.el.setAttribute("line", { visible: visible && this.hasPointingDevice });
   },
 
-  startInteractionAndForceCursorUpdate: function(touch) {
+  forceCursorUpdate: function() {
     // Update the ray and cursor positions
     const raycasterComp = this.el.components.raycaster;
     const raycaster = raycasterComp.raycaster;
     const camera = this.data.camera.components.camera.camera;
     const cursor = this.data.cursor;
-    this.mousePos.set(touch.clientX / window.innerWidth * 2 - 1, -(touch.clientY / window.innerHeight) * 2 + 1);
     raycaster.setFromCamera(this.mousePos, camera);
     this.el.setAttribute("raycaster", { origin: raycaster.ray.origin, direction: raycaster.ray.direction });
     raycasterComp.checkIntersections();
     const intersections = raycasterComp.intersections;
-    if (intersections.length === 0 || intersections[0].distance >= this.data.maxDistance) {
-      return false;
+    if (intersections.length === 0 || intersections[0].distance > this.data.maxDistance) {
+      this.currentTargetType = TARGET_TYPE_NONE;
+      return;
+    }
+    const intersection = intersections[0];
+    if (intersection.object.el.matches(".interactable, .interactable *")) {
+      this.currentTargetType = TARGET_TYPE_INTERACTABLE;
+    } else if (intersection.object.el.matches(".ui, .ui *")) {
+      this.currentTargetType = TARGET_TYPE_UI;
     }
-    cursor.object3D.position.copy(intersections[0].point);
+    cursor.object3D.position.copy(intersection.point);
     // Cursor position must be synced to physics before constraint is created
     cursor.components["static-body"].syncToPhysics();
-    cursor.emit("cursor-grab", {});
-    return true;
-  },
-
-  moveCursor: function(e) {
-    this.mousePos.set(e.clientX / window.innerWidth * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1);
-  },
-
-  endInteraction: function() {
-    this.data.cursor.emit("cursor-release", {});
   },
 
   startInteraction: function() {
@@ -232,24 +227,16 @@ AFRAME.registerComponent("cursor-controller", {
     return false;
   },
 
-  changeDistanceMod: function(delta) {
-    this.currentDistanceMod += delta;
+  moveCursor: function(x, y) {
+    this.mousePos.set(x, y);
   },
 
-  handleMouseWheel: function(e) {
-    if (this._isGrabbing()) {
-      switch (e.deltaMode) {
-        case e.DOM_DELTA_PIXEL:
-          this.currentDistanceMod += e.deltaY / 500;
-          break;
-        case e.DOM_DELTA_LINE:
-          this.currentDistanceMod += e.deltaY / 10;
-          break;
-        case e.DOM_DELTA_PAGE:
-          this.currentDistanceMod += e.deltaY / 2;
-          break;
-      }
-    }
+  endInteraction: function() {
+    this.data.cursor.emit("cursor-release", {});
+  },
+
+  changeDistanceMod: function(delta) {
+    this.currentDistanceMod += delta;
   },
 
   _handleEnterVR: function() {
diff --git a/src/hub.html b/src/hub.html
index 3bfed3456b0af449a617167eb383a2bbd8cd9d36..60ed2982cc8d1d9a8fc659ec6de50ce2a9825a84 100644
--- a/src/hub.html
+++ b/src/hub.html
@@ -184,7 +184,7 @@
                 ></a-entity>
             </template>
 
-            <a-mixin id="super-hands"
+            <a-mixin id="controller-super-hands"
                 super-hands="
                     colliderEvent: collisions; colliderEventProperty: els;
                     colliderEndEvent: collisions; colliderEndEventProperty: clearedEls;
@@ -295,7 +295,7 @@
                     missOpacity: 0.2;"
                 haptic-feedback
                 body="type: static; shape: none;"
-                mixin="super-hands"
+                mixin="controller-super-hands"
                 controls-shape-offset
             >
             </a-entity>
@@ -316,7 +316,7 @@
                     missOpacity: 0.2;"
                 haptic-feedback
                 body="type: static; shape: none;"
-                mixin="super-hands"
+                mixin="controller-super-hands"
                 controls-shape-offset
             ></a-entity>
 
diff --git a/src/hub.js b/src/hub.js
index a4ad2642a3e2d5c815feb424cbfee1a1db26445e..bf2470b1dc1a73e4dd9643641c7c82b3be5ac65b 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -312,7 +312,7 @@ const onReady = async () => {
         }
       };
       camera.addEventListener("componentinitialized", registerCameraController);
-      camera.setAttribute("camera-controller", "foo", "bar");
+      camera.setAttribute("camera-controller", "");
 
       const cursorEl = document.querySelector("#cursor-controller");
       if (cursorEl && cursorEl.components && cursorEl.components["cursor-controller"]) {
diff --git a/src/utils/mouse-events-handler.js b/src/utils/mouse-events-handler.js
index c46e57f5226394a05fdc0f28bd8b0220a0c9db9f..86ed25430c15fa66e059e19daf5733e869a9be6f 100644
--- a/src/utils/mouse-events-handler.js
+++ b/src/utils/mouse-events-handler.js
@@ -84,7 +84,17 @@ export default class MouseEventsHandler {
   }
 
   onMouseWheel(e) {
-    this.cursor.handleMouseWheel(e);
+    switch (e.deltaMode) {
+      case e.DOM_DELTA_PIXEL:
+        this.cursor.changeDistanceMod(e.deltaY / 500);
+        break;
+      case e.DOM_DELTA_LINE:
+        this.cursor.changeDistanceMod(e.deltaY / 10);
+        break;
+      case e.DOM_DELTA_PAGE:
+        this.cursor.changeDistanceMod(e.deltaY / 2);
+        break;
+    }
   }
 
   onMouseMove(e) {
@@ -93,7 +103,7 @@ export default class MouseEventsHandler {
       this.look(e);
     }
 
-    this.cursor.moveCursor(e);
+    this.cursor.moveCursor(e.clientX / window.innerWidth * 2 - 1, -(e.clientY / window.innerHeight) * 2 + 1);
   }
 
   onMouseUp(e) {
diff --git a/src/utils/primary-action-handler.js b/src/utils/primary-action-handler.js
index 01b65e3520016fabfa5f40e5848c9b1c6f1ad638..adb585e05a8492a6f0384cdfd8dad9f4827f7621 100644
--- a/src/utils/primary-action-handler.js
+++ b/src/utils/primary-action-handler.js
@@ -44,7 +44,7 @@ export default class PrimaryActionHandler {
   }
 
   onGrab(e) {
-    if (e.target.id.match(this.cursor.data.handedness)) {
+    if (this.cursor.controller && this.cursor.controller === e.target) {
       if (this.isCursorInteracting) {
         return;
       } else if (e.target.components["super-hands"].state.has("hover-start")) {
@@ -61,7 +61,7 @@ export default class PrimaryActionHandler {
   }
 
   onRelease(e) {
-    if (e.target.id.match(this.cursor.data.handedness) && this.isCursorInteracting) {
+    if (this.isCursorInteracting && this.cursor.controller && this.cursor.controller === e.target) {
       this.isCursorInteracting = false;
       this.cursor.endInteraction();
     } else {
@@ -70,7 +70,7 @@ export default class PrimaryActionHandler {
   }
 
   onPrimaryDown(e) {
-    if (e.target.id.match(this.cursor.data.handedness)) {
+    if (this.cursor.controller && this.cursor.controller === e.target) {
       if (this.isCursorInteracting) {
         return;
       } else if (e.target.components["super-hands"].state.has("hover-start")) {
@@ -88,7 +88,7 @@ export default class PrimaryActionHandler {
   }
 
   onPrimaryUp(e) {
-    if (e.target.id.match(this.cursor.data.handedness) && this.isCursorInteracting) {
+    if (this.isCursorInteracting && this.cursor.controller && this.cursor.controller === e.target) {
       this.isCursorInteracting = false;
       this.cursor.endInteraction();
       return;
diff --git a/src/utils/touch-events-handler.js b/src/utils/touch-events-handler.js
index 1b0a09885a6a40a80aa1794d9b549fc318dc051f..0c08a41acda37f3bb5c4a40aac8fd52920303325 100644
--- a/src/utils/touch-events-handler.js
+++ b/src/utils/touch-events-handler.js
@@ -73,8 +73,12 @@ export default class TouchEventsHandler {
     if (touch.clientY / window.innerHeight >= VIRTUAL_JOYSTICK_HEIGHT) {
       return;
     }
-    if (!this.touchReservedForCursor && this.cursor.startInteractionAndForceCursorUpdate(touch)) {
-      this.touchReservedForCursor = touch;
+    if (!this.touchReservedForCursor) {
+      this.cursor.moveCursor(touch.clientX / window.innerWidth * 2 - 1, -(touch.clientY / window.innerHeight) * 2 + 1);
+      this.cursor.forceCursorUpdate();
+      if (this.cursor.startInteraction()) {
+        this.touchReservedForCursor = touch;
+      }
     }
     this.touches.push(touch);
   }
@@ -89,7 +93,7 @@ export default class TouchEventsHandler {
 
   singleTouchMove(touch) {
     if (this.touchReservedForCursor && touch.identifier === this.touchReservedForCursor.identifier) {
-      this.cursor.moveCursor(touch);
+      this.cursor.moveCursor(touch.clientX / window.innerWidth * 2 - 1, -(touch.clientY / window.innerHeight) * 2 + 1);
       return;
     }
     if (touch.clientY / window.innerHeight >= VIRTUAL_JOYSTICK_HEIGHT) return;
@@ -117,7 +121,10 @@ export default class TouchEventsHandler {
     }
     if (touch.identifier === this.touchReservedForLookControls.identifier) {
       if (!this.touchReservedForCursor) {
-        this.cursor.moveCursor(touch);
+        this.cursor.moveCursor(
+          touch.clientX / window.innerWidth * 2 - 1,
+          -(touch.clientY / window.innerHeight) * 2 + 1
+        );
       }
       this.look(this.touchReservedForLookControls, touch);
       this.touchReservedForLookControls = touch;