|
@@ -3,7 +3,7 @@ import time
|
|
|
from threading import Thread
|
|
from threading import Thread
|
|
|
from math import atan2, pi
|
|
from math import atan2, pi
|
|
|
|
|
|
|
|
-from tuio_server import TuiListener
|
|
|
|
|
|
|
+from tuio_server import TuioServer2D
|
|
|
from logger import Logger
|
|
from logger import Logger
|
|
|
from events import TapEvent, FlickEvent, RotateEvent, PinchEvent, PanEvent
|
|
from events import TapEvent, FlickEvent, RotateEvent, PinchEvent, PanEvent
|
|
|
|
|
|
|
@@ -25,8 +25,8 @@ def add(a, b):
|
|
|
class TouchPoint(object):
|
|
class TouchPoint(object):
|
|
|
def __init__(self, sid, x, y):
|
|
def __init__(self, sid, x, y):
|
|
|
self.sid = sid
|
|
self.sid = sid
|
|
|
- self.x = x
|
|
|
|
|
- self.y = y
|
|
|
|
|
|
|
+ self.px = self.x = x
|
|
|
|
|
+ self.py = self.y = y
|
|
|
|
|
|
|
|
def update(self, x, y):
|
|
def update(self, x, y):
|
|
|
self.px = self.x
|
|
self.px = self.x
|
|
@@ -35,7 +35,10 @@ class TouchPoint(object):
|
|
|
self.y = y
|
|
self.y = y
|
|
|
|
|
|
|
|
def distance_to(self, other_x, other_y):
|
|
def distance_to(self, other_x, other_y):
|
|
|
- return distance(self.x, self.y, other_x, other_y)
|
|
|
|
|
|
|
+ return distance((self.x, self.y), (other_x, other_y))
|
|
|
|
|
+
|
|
|
|
|
+ def distance_to_prev(self):
|
|
|
|
|
+ return self.distance_to(self.px, self.py)
|
|
|
|
|
|
|
|
def init_gesture_data(self, cx, cy):
|
|
def init_gesture_data(self, cx, cy):
|
|
|
self.pinch = self.old_pinch = self.distance_to(cx, cy)
|
|
self.pinch = self.old_pinch = self.distance_to(cx, cy)
|
|
@@ -62,7 +65,6 @@ class TouchPoint(object):
|
|
|
# Heuristic constants
|
|
# Heuristic constants
|
|
|
# TODO: Encapsulate DPI resolution in distance heuristics
|
|
# TODO: Encapsulate DPI resolution in distance heuristics
|
|
|
SUPPORTED_GESTURES = ('tap', 'pan', 'flick', 'rotate', 'pinch')
|
|
SUPPORTED_GESTURES = ('tap', 'pan', 'flick', 'rotate', 'pinch')
|
|
|
-TUIO_ADDRESS = ('localhost', 3333)
|
|
|
|
|
DOUBLE_TAP_DISTANCE_THRESHOLD = 30
|
|
DOUBLE_TAP_DISTANCE_THRESHOLD = 30
|
|
|
FLICK_VELOCITY_TRESHOLD = 20
|
|
FLICK_VELOCITY_TRESHOLD = 20
|
|
|
TAP_INTERVAL = .200
|
|
TAP_INTERVAL = .200
|
|
@@ -71,24 +73,31 @@ MAX_MULTI_DRAG_DISTANCE = 100
|
|
|
|
|
|
|
|
|
|
|
|
|
class MultiTouchListener(Logger):
|
|
class MultiTouchListener(Logger):
|
|
|
- def __init__(self, verbose=0, update_rate=60, **kwargs):
|
|
|
|
|
|
|
+ def __init__(self, update_rate=60, verbose=0, tuio_verbose=0, **kwargs):
|
|
|
super(MultiTouchListener, self).__init__(**kwargs)
|
|
super(MultiTouchListener, self).__init__(**kwargs)
|
|
|
self.verbose = verbose
|
|
self.verbose = verbose
|
|
|
|
|
+ self.tuio_verbose = tuio_verbose
|
|
|
self.last_tap = 0
|
|
self.last_tap = 0
|
|
|
self.update_rate = update_rate
|
|
self.update_rate = update_rate
|
|
|
self.points_changed = False
|
|
self.points_changed = False
|
|
|
self.handlers = {}
|
|
self.handlers = {}
|
|
|
|
|
|
|
|
# Session id's pointing to point coordinates
|
|
# Session id's pointing to point coordinates
|
|
|
- self.points = {}
|
|
|
|
|
|
|
+ self.points = []
|
|
|
|
|
|
|
|
self.taps_down = []
|
|
self.taps_down = []
|
|
|
self.taps = []
|
|
self.taps = []
|
|
|
|
|
+ self.centroid = (0, 0)
|
|
|
|
|
|
|
|
def update_centroid(self):
|
|
def update_centroid(self):
|
|
|
self.old_centroid = self.centroid
|
|
self.old_centroid = self.centroid
|
|
|
- cx, cy = zip(*[(p.x, p.y) for p in self.points])
|
|
|
|
|
l = len(self.points)
|
|
l = len(self.points)
|
|
|
|
|
+
|
|
|
|
|
+ if not l:
|
|
|
|
|
+ self.centroid = (0, 0)
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ cx, cy = zip(*[(p.x, p.y) for p in self.points])
|
|
|
self.centroid = (reduce(add, cx, 0) / l, reduce(add, cy, 0) / l)
|
|
self.centroid = (reduce(add, cx, 0) / l, reduce(add, cy, 0) / l)
|
|
|
|
|
|
|
|
def analyze(self):
|
|
def analyze(self):
|
|
@@ -148,7 +157,7 @@ class MultiTouchListener(Logger):
|
|
|
|
|
|
|
|
rotation += da
|
|
rotation += da
|
|
|
|
|
|
|
|
- p.set_pinch(distance(p.x, p.y, cx, cy))
|
|
|
|
|
|
|
+ p.set_pinch(p.distance_to(cx, cy))
|
|
|
pinch += p.pinch_diff()
|
|
pinch += p.pinch_diff()
|
|
|
|
|
|
|
|
if rotation:
|
|
if rotation:
|
|
@@ -166,7 +175,8 @@ class MultiTouchListener(Logger):
|
|
|
m = MAX_MULTI_DRAG_DISTANCE
|
|
m = MAX_MULTI_DRAG_DISTANCE
|
|
|
clustered = l == 1 or all([p.distance_to(*self.centroid) <= m \
|
|
clustered = l == 1 or all([p.distance_to(*self.centroid) <= m \
|
|
|
for p in self.points])
|
|
for p in self.points])
|
|
|
- directions = [(cmp(p.dx(), 0), cmp(p.dy(), 0)) 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 any(map(all, zip(*directions))) and clustered:
|
|
|
if l == 1:
|
|
if l == 1:
|
|
@@ -180,10 +190,11 @@ class MultiTouchListener(Logger):
|
|
|
self.trigger(PanEvent(cx, cy, dx, dy, l))
|
|
self.trigger(PanEvent(cx, cy, dx, dy, l))
|
|
|
|
|
|
|
|
def point_down(self, sid, x, y):
|
|
def point_down(self, sid, x, y):
|
|
|
- if sid in self.points:
|
|
|
|
|
- raise KeyError('Point with session id "%s" already exists.' % sid)
|
|
|
|
|
|
|
+ if self.find_point(sid):
|
|
|
|
|
+ raise ValueError('Point with session id "%s" already exists.' % sid)
|
|
|
|
|
|
|
|
- self.points[sid] = p = TouchPoint(sid, x, y)
|
|
|
|
|
|
|
+ p = TouchPoint(sid, x, y)
|
|
|
|
|
+ self.points.append(p)
|
|
|
self.update_centroid()
|
|
self.update_centroid()
|
|
|
|
|
|
|
|
# Detect multi-point gestures
|
|
# Detect multi-point gestures
|
|
@@ -191,24 +202,32 @@ class MultiTouchListener(Logger):
|
|
|
p.init_gesture_data(*self.centroid)
|
|
p.init_gesture_data(*self.centroid)
|
|
|
|
|
|
|
|
if len(self.points) == 2:
|
|
if len(self.points) == 2:
|
|
|
- first_p = self.points[0]
|
|
|
|
|
- first_p.init_gesture_data(*self.centroid)
|
|
|
|
|
|
|
+ self.points[0].init_gesture_data(*self.centroid)
|
|
|
|
|
|
|
|
self.taps_down.append(p)
|
|
self.taps_down.append(p)
|
|
|
self.last_tap = time.time()
|
|
self.last_tap = time.time()
|
|
|
self.points_changed = True
|
|
self.points_changed = True
|
|
|
|
|
|
|
|
|
|
+ def find_point(self, sid, index=False):
|
|
|
|
|
+ for i, p in enumerate(self.points):
|
|
|
|
|
+ if p.sid == sid:
|
|
|
|
|
+ return (i, p) if index else p
|
|
|
|
|
+
|
|
|
|
|
+ if index:
|
|
|
|
|
+ return -1, None
|
|
|
|
|
+
|
|
|
def point_up(self, sid):
|
|
def point_up(self, sid):
|
|
|
- if sid not in self.points:
|
|
|
|
|
|
|
+ i, p = self.find_point(sid, index=True)
|
|
|
|
|
+
|
|
|
|
|
+ if not p:
|
|
|
raise KeyError('No point with session id "%s".' % sid)
|
|
raise KeyError('No point with session id "%s".' % sid)
|
|
|
|
|
|
|
|
- p = self.points[sid]
|
|
|
|
|
- del self.points[sid]
|
|
|
|
|
|
|
+ del self.points[i]
|
|
|
|
|
|
|
|
# Tap/flick detection
|
|
# Tap/flick detection
|
|
|
if p in self.taps_down:
|
|
if p in self.taps_down:
|
|
|
# Detect if Flick based on movement
|
|
# Detect if Flick based on movement
|
|
|
- if distance(p.x, p.y, p.px, p.py) > FLICK_VELOCITY_TRESHOLD:
|
|
|
|
|
|
|
+ if p.distance_to_prev() > FLICK_VELOCITY_TRESHOLD:
|
|
|
self.trigger(FlickEvent(p.px, p.py, (p.dx(), p.dy())))
|
|
self.trigger(FlickEvent(p.px, p.py, (p.dx(), p.dy())))
|
|
|
else:
|
|
else:
|
|
|
if time.time() - self.last_tap < TAP_INTERVAL:
|
|
if time.time() - self.last_tap < TAP_INTERVAL:
|
|
@@ -219,18 +238,12 @@ class MultiTouchListener(Logger):
|
|
|
self.points_changed = True
|
|
self.points_changed = True
|
|
|
|
|
|
|
|
def point_move(self, sid, x, y):
|
|
def point_move(self, sid, x, y):
|
|
|
- self.points[sid].update(x, y)
|
|
|
|
|
|
|
+ self.find_point(sid).update(x, y)
|
|
|
self.points_changed = True
|
|
self.points_changed = True
|
|
|
|
|
|
|
|
- def _tuio_handler(self, addr, tags, data, source):
|
|
|
|
|
- # TODO: Call self.point_{down,up,move}
|
|
|
|
|
- pass
|
|
|
|
|
-
|
|
|
|
|
def _tuio_server(self):
|
|
def _tuio_server(self):
|
|
|
- server = OSCServer(TUIO_ADDRESS)
|
|
|
|
|
- server.addDefaultHandlers()
|
|
|
|
|
- server.addMsgHandler('/tuio', self._tuio_handler)
|
|
|
|
|
- server.serve_forever()
|
|
|
|
|
|
|
+ server = TuioServer2D(self, verbose=self.tuio_verbose)
|
|
|
|
|
+ server.start()
|
|
|
|
|
|
|
|
def start(self):
|
|
def start(self):
|
|
|
"""
|
|
"""
|
|
@@ -275,6 +288,6 @@ if __name__ == '__main__':
|
|
|
def tap(event):
|
|
def tap(event):
|
|
|
print 'tap:', event
|
|
print 'tap:', event
|
|
|
|
|
|
|
|
- loop = MultiTouchListener(verbose=2)
|
|
|
|
|
|
|
+ loop = MultiTouchListener(verbose=2, tuio_verbose=1)
|
|
|
loop.bind('tap', tap)
|
|
loop.bind('tap', tap)
|
|
|
loop.start()
|
|
loop.start()
|