Procházet zdrojové kódy

Started implementing TUIO listener class.

Taddeus Kroes před 14 roky
rodič
revize
5132ece776
4 změnil soubory, kde provedl 266 přidání a 0 odebrání
  1. 4 0
      src/multitouch/__init__.py
  2. 25 0
      src/multitouch/events.py
  3. 220 0
      src/multitouch/touch.py
  4. 17 0
      src/tuio_test.py

+ 4 - 0
src/multitouch/__init__.py

@@ -0,0 +1,4 @@
+from events import TapEvent, FlickEvent
+from touch import MultiTouchListener
+
+__all__ = ('MultiTouchListener', 'TapEvent', 'FlickEvent')

+ 25 - 0
src/multitouch/events.py

@@ -0,0 +1,25 @@
+class GestureEvent(object):
+    def __init__(self, gesture):
+        self.gesture = gesture
+
+
+def TapEvent(GestureEvent):
+    def __init__(self, x, y):
+        super(TapEvent, self).__init__('tap')
+        self.x = x
+        self.y = y
+
+    def __str__(self):
+        return '<%s (%s, %s)>' % (self.__class__.__name__, self.x, self.y)
+
+
+def FlickEvent(GestureEvent):
+    def __init__(self, x, y, velocity):
+        super(TapEvent, self).__init__('flick')
+        self.x = x
+        self.y = y
+        self.velocity = velocity
+
+    def __str__(self):
+        return '<%s (%s, %s) velocity=%s>' % \
+               (self.__class__.__name__, self.x, self.y, self.velocity)

+ 220 - 0
src/multitouch/touch.py

@@ -0,0 +1,220 @@
+#!/usr/bin/env python
+from events import TapEvent, FlickEvent
+import time
+from math import atan2
+from threading import Thread
+from OSC import OSCServer
+
+
+def distance(a, b):
+    """
+    Calculate the distance between points a and b.
+    """
+    xa, ya = a
+    xb, yb = b
+
+    return ((xb - xa) ** 2 + (yb - ya) ** 2) ** .5
+
+
+def add(a, b):
+    return a + b
+
+
+class TouchPoint(object):
+    def __init__(self, sid, x, y):
+        self.sid = sid
+        self.x = x
+        self.y = y
+
+    def update(self, x, y):
+        self.px = self.x
+        self.py = self.y
+        self.x = x
+        self.y = y
+
+    def init_gesture_data(self, cx, cy):
+        self.pinch = self.old_pinch = distance(self.x, self.y, cx, cy)
+        self.angle = self.old_angle = atan2(self.y - cy, self.x - cx)
+
+    def set_angle(self, angle):
+        self.old_angle = self.angle
+        self.angle = angle
+
+    def set_pinch(self, pinch):
+        self.old_pinch = self.pinch
+        self.pinch = pinch
+
+    def dx(self):
+        return int(self.x - self.px)
+
+    def dy(self):
+        return int(self.y - self.py)
+
+
+SUPPORTED_GESTURES = ('tap', 'pan', 'flick', 'rotate', 'pinch')
+TUIO_ADDRESS = ('localhost', 3333)
+DOUBLE_TAP_DISTANCE = 30
+TAP_INTERVAL = .200
+TAP_TIMEOUT = .200
+
+
+class MultiTouchListener(object):
+    def __init__(self, verbose=False, update_rate=60):
+        self.verbose = verbose
+        self.last_tap = 0
+        self.update_rate = update_rate
+        self.points_changed = False
+        self.handlers = {}
+
+        # Session id's pointing to point coordinates
+        self.points = {}
+
+        self.taps_down = []
+        self.taps = []
+
+    def update_centroid(self):
+        self.old_centroid = self.centroid
+        cx, cy = zip(*[(p.x, p.y) for p in self.points])
+        l = len(self.points)
+        self.centroid = (reduce(add, cx, 0) / l, reduce(add, cy, 0) / l)
+
+    def analyze(self):
+        self.detect_taps()
+
+        if self.points_changed:
+            self.update_centroid()
+
+            # Do not try to rotate or pinch while dragging
+            # This gets rid of a lot of jittery events
+            if not self.detect_drag():
+                self.detect_rotation()
+                self.detect_pinch()
+
+            self.points_changed = False
+
+    def detect_taps(self):
+        if len(self.taps) == 2:
+            if distance(*self.taps) > DOUBLE_TAP_DISTANCE:
+                # Taps are too far away too be a double tap, add 2 separate
+                # events
+                self.trigger(TapEvent(*self.taps[0]))
+                self.trigger(TapEvent(*self.taps[1]))
+            else:
+                # Distance is within treshold, trigger a 'double tap' event
+                self.trigger(TapEvent(*self.taps[0], double=True))
+
+            self.taps = []
+        elif len(self.taps) == 1:
+            # FIXME: Ignore successive single- and double taps?
+            if time.time() - self.last_tap > TAP_TIMEOUT:
+                self.trigger(TapEvent(*self.taps[0]))
+                self.taps = []
+
+    def detect_rotation(self):
+        if 'rotate' not in self.handlers:
+            return
+
+    def detect_pinch(self):
+        if 'pinch' not in self.handlers:
+            return
+
+    def point_down(self, sid, x, y):
+        if sid in self.points:
+            raise KeyError('Point with session id "%s" already exists.' % sid)
+
+        self.points[sid] = TouchPoint(sid, x, y)
+        self.update_centroid()
+
+        # Detect multi-point gestures
+        if len(self.points) > 1:
+            p.init_gesture_data(*self.centroid)
+
+            if len(self.points) == 2:
+                first_p = self.points[0]
+                first_p.init_gesture_data(*self.centroid)
+
+        self.taps_down.append(p)
+        self.last_tap = time.time()
+        self.points_changed = True
+
+    def point_up(self, sid):
+        if sid not in self.points:
+            raise KeyError('No point with session id "%s".' % sid)
+
+        p = self.points[sid]
+        del self.points[sid]
+
+        # Tap/flick detection
+        if p in self.taps_down:
+            # Detect if Flick based on movement
+            if distance(p.x, p.y, p.px, p.py) > FLICK_VELOCITY_TRESHOLD:
+                self.trigger(FlickEvent(p.px, p.py, (p.dx(), p.dy())))
+            else:
+                if time.time() - self.last_tap < TAP_INTERVAL:
+                    # Move from taps_down to taps for us in tap detection
+                    self.taps_down.remove(p)
+                    self.taps.append((p.x, p.y))
+
+        self.points_changed = True
+
+    def point_move(self, sid, x, y):
+        self.points[sid].update(x, y)
+        self.points_changed = True
+
+    def _tuio_handler(self, addr, tags, data, source):
+        # TODO: Call self.point_{down,up,move}
+        pass
+
+    def _tuio_server(self):
+        server = OSCServer(TUIO_ADDRESS)
+        server.addDefaultHandlers()
+        server.addMsgHandler('/tuio', self._tuio_handler)
+        server.serve_forever()
+
+    def start(self):
+        """
+        Start event loop.
+        """
+        self.log('Starting event loop')
+
+        try:
+            # Run TUIO message listener in a different thread
+            #thread = Thread(target=self.__class__._tuio_server, args=(self))
+            thread = Thread(target=self._tuio_server)
+            thread.daemon = True
+            thread.start()
+
+            interval = 1. / self.update_rate
+
+            while True:
+                self.analyze()
+                time.sleep(interval)
+        except KeyboardInterrupt:
+            self.log('Stopping event loop')
+
+    def log(self, msg):
+        if self.verbose:
+            print '| LOG | %s' % msg
+
+    def bind(self, gesture, handler):
+        if gesture not in SUPPORTED_GESTURES:
+            raise ValueError('Unsopported gesture "%s".' % gesture)
+
+        if gesture not in self.handlers:
+            self.handlers[gesture] = []
+
+        self.handlers[gesture].append(handler)
+
+    def trigger(self, event):
+        if event.gesture in self.handlers:
+            for handler in self.handlers[event.gesture]:
+                handler(event)
+
+
+if __name__ == '__main__':
+    def tap(event):
+        print 'tap:', event
+
+    loop = MultiTouchListener(verbose=True)
+    loop.bind('tap', tap)
+    loop.start()

+ 17 - 0
src/tuio_test.py

@@ -0,0 +1,17 @@
+#!/usr/bin/env python
+from OSC import OSCServer
+
+
+def tuio_handler(addr, tags, data, source):
+    print 'Received message:'
+    print 'addr:', addr
+    print 'tags:', tags
+    print 'data:', data
+    print 'source:', source
+
+
+if __name__ == '__main__':
+    tuio_address = 'localhost', 3333
+    server = OSCServer(tuio_address)
+    server.addDefaultHandlers()
+    server.addMsgHandler('/tuio', tuio_handler)