Browse Source

Finished rewriting TouchProcessor.pde.

Taddeus Kroes 14 years ago
parent
commit
ea38f361d9
2 changed files with 114 additions and 14 deletions
  1. 41 1
      src/multitouch/events.py
  2. 73 13
      src/multitouch/touch.py

+ 41 - 1
src/multitouch/events.py

@@ -15,7 +15,7 @@ def TapEvent(GestureEvent):
 
 def FlickEvent(GestureEvent):
     def __init__(self, x, y, velocity):
-        super(TapEvent, self).__init__('flick')
+        super(FlickEvent, self).__init__('flick')
         self.x = x
         self.y = y
         self.velocity = velocity
@@ -23,3 +23,43 @@ def FlickEvent(GestureEvent):
     def __str__(self):
         return '<%s (%s, %s) velocity=%s>' % \
                (self.__class__.__name__, self.x, self.y, self.velocity)
+
+
+def RotateEvent(GestureEvent):
+    def __init__(self, cx, cy, angle, n):
+        super(RotateEvent, self).__init__('rotate')
+        self.cx = cx
+        self.cy = cy
+        self.angle = angle
+        self.n = n
+
+    def __str__(self):
+        return '<%s (%s, %s) angle=%s n=%d>' % \
+               (self.__class__.__name__, self.x, self.y, self.angle, self.n)
+
+
+def PinchEvent(GestureEvent):
+    def __init__(self, cx, cy, amount, n):
+        super(RotateEvent, self).__init__('pinch')
+        self.cx = cx
+        self.cy = cy
+        self.amount = amount
+        self.n = n
+
+    def __str__(self):
+        return '<%s (%s, %s) amount=%s n=%d>' % \
+               (self.__class__.__name__, self.x, self.y, self.amount, self.n)
+
+
+def PanEvent(GestureEvent):
+    def __init__(self, x, y, dx, dy, n):
+        super(RotateEvent, self).__init__('pan')
+        self.x = x
+        self.y = y
+        self.dx = dx
+        self.dy = dy
+        self.n = n
+
+    def __str__(self):
+        return '<%s (%s, %s) amount=%s n=%d>' % \
+               (self.__class__.__name__, self.x, self.y, self.amount, self.n)

+ 73 - 13
src/multitouch/touch.py

@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-from events import TapEvent, FlickEvent
+from events import TapEvent, FlickEvent, RotateEvent, PinchEvent, PanEvent
 import time
 from math import atan2
 from threading import Thread
@@ -32,8 +32,11 @@ class TouchPoint(object):
         self.x = x
         self.y = y
 
+    def distance_to(self, other_x, other_y):
+        return distance(self.x, self.y, other_x, other_y)
+
     def init_gesture_data(self, cx, cy):
-        self.pinch = self.old_pinch = distance(self.x, self.y, cx, cy)
+        self.pinch = self.old_pinch = self.distance_to(cx, cy)
         self.angle = self.old_angle = atan2(self.y - cy, self.x - cx)
 
     def set_angle(self, angle):
@@ -44,6 +47,9 @@ class TouchPoint(object):
         self.old_pinch = self.pinch
         self.pinch = pinch
 
+    def angle_diff(self):
+        return self.angle - self.old_angle
+
     def dx(self):
         return int(self.x - self.px)
 
@@ -51,11 +57,14 @@ class TouchPoint(object):
         return int(self.y - self.py)
 
 
+# Heuristic constants
+# TODO: Encapsulate DPI resolution in distance heuristics
 SUPPORTED_GESTURES = ('tap', 'pan', 'flick', 'rotate', 'pinch')
 TUIO_ADDRESS = ('localhost', 3333)
 DOUBLE_TAP_DISTANCE = 30
 TAP_INTERVAL = .200
 TAP_TIMEOUT = .200
+MAX_MULTI_DRAG_DISTANCE = 100
 
 
 class MultiTouchListener(object):
@@ -84,11 +93,10 @@ class MultiTouchListener(object):
         if self.points_changed:
             self.update_centroid()
 
-            # Do not try to rotate or pinch while dragging
+            # Do not try to rotate or pinch while panning
             # This gets rid of a lot of jittery events
-            if not self.detect_drag():
-                self.detect_rotation()
-                self.detect_pinch()
+            if not self.detect_pan():
+                self.detect_rotation_and_pinch()
 
             self.points_changed = False
 
@@ -110,14 +118,63 @@ class MultiTouchListener(object):
                 self.trigger(TapEvent(*self.taps[0]))
                 self.taps = []
 
-    def detect_rotation(self):
-        if 'rotate' not in self.handlers:
-            return
+    def detect_rotation_and_pinch(self):
+        """
+        Rotation is the average angle change between each point and the
+        centroid. Pinch is the average distance change from the points to the
+        centroid.
+        """
+        l = len(self.points)
 
-    def detect_pinch(self):
-        if 'pinch' not in self.handlers:
+        if 'pinch' not in self.handlers or l < 2:
             return
 
+        rotation = pinch = 0
+
+        for p in self.points:
+            p.set_angle(atan2(p.y - cy, p.x - cx))
+            da = p.angle_diff()
+
+            # Assert that angle is in [-PI, PI]
+            if da > PI:
+                da -= 2 * PI
+            elif da < PI:
+                da += 2 * PI
+
+            rotation += da
+
+            p.set_pinch(distance(p.x, p.y, cx, cy))
+            pinch += p.pinch_diff()
+
+        if rotation:
+            self.trigger(RotateEvent(cx, cy, rotation / l, l))
+
+        if pinch:
+            self.trigger(PinchEvent(cx, cy, pinch / l, l))
+
+    def detect_pan(self):
+        """
+        Look for multi-finger drag events. Multi-drag is defined as all the
+        fingers moving close-ish together in the same direction.
+        """
+        l = len(self.points)
+        m = MAX_MULTI_DRAG_DISTANCE
+        clustered = l == 1 or all([p.distance_to(*self.centroid) <= m \
+                                   for p in self.points])
+        directions = [(cmp(p.dx(), 0), cmp(p.dy(), 0)) for p in self.points]
+
+
+        if any(map(all, zip(*directions))) and clustered:
+            if l == 1:
+                p = self.points[0]
+                cx, cy, dx, dy = p.x, p.y, p.dx(), p.dy()
+            else:
+                cx, cy = self.centroid
+                old_cx, old_cy = self.old_centroid
+                dx, dy = cx - old_cx, cy - old_cy
+
+            self.trigger(PanEvent(cx, cy, dx, dy, l))
+
     def point_down(self, sid, x, y):
         if sid in self.points:
             raise KeyError('Point with session id "%s" already exists.' % sid)
@@ -198,7 +255,7 @@ class MultiTouchListener(object):
 
     def bind(self, gesture, handler):
         if gesture not in SUPPORTED_GESTURES:
-            raise ValueError('Unsopported gesture "%s".' % gesture)
+            raise ValueError('Unsupported gesture "%s".' % gesture)
 
         if gesture not in self.handlers:
             self.handlers[gesture] = []
@@ -207,7 +264,10 @@ class MultiTouchListener(object):
 
     def trigger(self, event):
         if event.gesture in self.handlers:
-            for handler in self.handlers[event.gesture]:
+            h = self.handlers[event.gesture]
+            self.log('Event triggered: "%s" (%d handlers)' % (event, len(h)))
+
+            for handler in h:
                 handler(event)