Commit 5132ece7 authored by Taddeus Kroes's avatar Taddeus Kroes

Started implementing TUIO listener class.

parent 09114642
from events import TapEvent, FlickEvent
from touch import MultiTouchListener
__all__ = ('MultiTouchListener', 'TapEvent', 'FlickEvent')
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)
#!/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()
#!/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)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment