|
@@ -71,6 +71,12 @@ class TouchPoint(object):
|
|
|
def xy(self):
|
|
def xy(self):
|
|
|
return self.x, self.y
|
|
return self.x, self.y
|
|
|
|
|
|
|
|
|
|
+ def dx(self):
|
|
|
|
|
+ return self.x - self.px
|
|
|
|
|
+
|
|
|
|
|
+ def dy(self):
|
|
|
|
|
+ return self.y- self.py
|
|
|
|
|
+
|
|
|
def update(self, x, y):
|
|
def update(self, x, y):
|
|
|
self.update_time = time.time()
|
|
self.update_time = time.time()
|
|
|
self.px = self.x
|
|
self.px = self.x
|
|
@@ -78,43 +84,21 @@ class TouchPoint(object):
|
|
|
self.x = x
|
|
self.x = x
|
|
|
self.y = y
|
|
self.y = y
|
|
|
|
|
|
|
|
- def distance_to(self, 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 set_centroid(self, cx, cy):
|
|
|
|
|
- self.pinch = self.distance_to(cx, cy)
|
|
|
|
|
- self.angle = atan2(cy - self.y, self.x - cx)
|
|
|
|
|
-
|
|
|
|
|
- if self.pinch != None:
|
|
|
|
|
- self.old_pinch = self.pinch
|
|
|
|
|
- self.old_angle = self.angle
|
|
|
|
|
- self.pinch = pinch
|
|
|
|
|
- self.angle = angle
|
|
|
|
|
- else:
|
|
|
|
|
- self.old_pinch = self.pinch = pinch
|
|
|
|
|
- self.old_angle = self.angle = angle
|
|
|
|
|
-
|
|
|
|
|
- def angle_diff(self):
|
|
|
|
|
- return self.angle - self.old_angle
|
|
|
|
|
-
|
|
|
|
|
- def pinch_diff(self):
|
|
|
|
|
- return self.pinch - self.old_pinch
|
|
|
|
|
-
|
|
|
|
|
- def dx(self):
|
|
|
|
|
- return self.x - self.px
|
|
|
|
|
-
|
|
|
|
|
- def dy(self):
|
|
|
|
|
- return self.y - self.py
|
|
|
|
|
-
|
|
|
|
|
def down_time(self):
|
|
def down_time(self):
|
|
|
return time.time() - self.start_time
|
|
return time.time() - self.start_time
|
|
|
|
|
|
|
|
|
|
+ def angle(self, cx, cy):
|
|
|
|
|
+ return atan2(cy - self.y, self.x - cx)
|
|
|
|
|
+
|
|
|
|
|
+ def distance(self, other_x, other_y):
|
|
|
|
|
+ return distance((self.x, self.y), (other_x, other_y))
|
|
|
|
|
+
|
|
|
def is_tap(self):
|
|
def is_tap(self):
|
|
|
- return self.distance_to_prev() < TAP_TIME \
|
|
|
|
|
- and self.distance_to(self.start_x, self.start_y) < TAP_DISTANCE
|
|
|
|
|
|
|
+ return time.time() - self.update_time < TAP_TIME \
|
|
|
|
|
+ and self.distance(self.start_x, self.start_y) < TAP_DISTANCE
|
|
|
|
|
+
|
|
|
|
|
+ def distance_to_prev(self):
|
|
|
|
|
+ return self.distance(self.px, self.py)
|
|
|
|
|
|
|
|
def is_stationary(self):
|
|
def is_stationary(self):
|
|
|
return self.distance_to_prev() < DIST_THRESHOLD
|
|
return self.distance_to_prev() < DIST_THRESHOLD
|
|
@@ -134,10 +118,12 @@ class MultiTouchListener(Logger):
|
|
|
self.points = []
|
|
self.points = []
|
|
|
|
|
|
|
|
# Put centroid outside screen to prevent misinterpretation
|
|
# Put centroid outside screen to prevent misinterpretation
|
|
|
- self.centroid = (-1., -1.)
|
|
|
|
|
|
|
+ self.centroid = -1., -1.
|
|
|
|
|
|
|
|
self.server = TuioServer2D(self, verbose=tuio_verbose)
|
|
self.server = TuioServer2D(self, verbose=tuio_verbose)
|
|
|
|
|
|
|
|
|
|
+ self.angle = self.pinch = None
|
|
|
|
|
+
|
|
|
def point_down(self, sid, x, y):
|
|
def point_down(self, sid, x, y):
|
|
|
"""
|
|
"""
|
|
|
Called by TUIO listener when a new touch point is created, triggers a
|
|
Called by TUIO listener when a new touch point is created, triggers a
|
|
@@ -177,7 +163,7 @@ class MultiTouchListener(Logger):
|
|
|
t = time.time()
|
|
t = time.time()
|
|
|
|
|
|
|
|
if t - self.last_tap_time < TAP_TIMEOUT \
|
|
if t - self.last_tap_time < TAP_TIMEOUT \
|
|
|
- and p.distance_to(*self.last_tap.xy) < DOUBLE_TAP_DISTANCE:
|
|
|
|
|
|
|
+ and p.distance(*self.last_tap.xy) < DOUBLE_TAP_DISTANCE:
|
|
|
self.trigger(DoubleTap(p.x, p.y))
|
|
self.trigger(DoubleTap(p.x, p.y))
|
|
|
self.last_tap = None
|
|
self.last_tap = None
|
|
|
self.last_tap_time = 0
|
|
self.last_tap_time = 0
|
|
@@ -200,7 +186,7 @@ class MultiTouchListener(Logger):
|
|
|
"""
|
|
"""
|
|
|
p = self.find_point(sid)
|
|
p = self.find_point(sid)
|
|
|
|
|
|
|
|
- if p.distance_to(x, y) > DIST_THRESHOLD:
|
|
|
|
|
|
|
+ if p.distance(x, y) > DIST_THRESHOLD:
|
|
|
p.update(x, y)
|
|
p.update(x, y)
|
|
|
self.trigger(MoveEvent(p))
|
|
self.trigger(MoveEvent(p))
|
|
|
self.points_changed = True
|
|
self.points_changed = True
|
|
@@ -215,33 +201,77 @@ class MultiTouchListener(Logger):
|
|
|
|
|
|
|
|
if l < 2 or ('pinch' not in self.handlers \
|
|
if l < 2 or ('pinch' not in self.handlers \
|
|
|
and 'rotate' not in self.handlers):
|
|
and 'rotate' not in self.handlers):
|
|
|
|
|
+ self.set_state(None, None)
|
|
|
return
|
|
return
|
|
|
|
|
|
|
|
|
|
+ self.calc_state()
|
|
|
|
|
+ rotation, pinch = self.state_diff()
|
|
|
cx, cy = self.centroid
|
|
cx, cy = self.centroid
|
|
|
- #rotation = pinch = 0
|
|
|
|
|
|
|
|
|
|
- #for p in self.points:
|
|
|
|
|
- # da = p.angle_diff()
|
|
|
|
|
|
|
+ if rotation:
|
|
|
|
|
+ self.trigger(Rotate(cx, cy, rotation, l))
|
|
|
|
|
|
|
|
- # # Assert that angle is in [-pi, pi]
|
|
|
|
|
- # if da > pi:
|
|
|
|
|
- # da -= 2 * pi
|
|
|
|
|
- # elif da < pi:
|
|
|
|
|
- # da += 2 * pi
|
|
|
|
|
|
|
+ if pinch:
|
|
|
|
|
+ self.trigger(Pinch(cx, cy, pinch, l))
|
|
|
|
|
|
|
|
- # rotation += da
|
|
|
|
|
- # pinch += p.pinch_diff()
|
|
|
|
|
|
|
+ def calc_state(self):
|
|
|
|
|
+ l = len(self.points)
|
|
|
|
|
+ cx, cy = self.centroid
|
|
|
|
|
+ angle = reduce(add, [p.angle(cx, cy) for p in self.points]) / l
|
|
|
|
|
+ distance = reduce(add, [p.distance(cx, cy) for p in self.points]) / l
|
|
|
|
|
|
|
|
- angles, pinches = zip(*[(p.angle_diff(), p.pinch_diff())
|
|
|
|
|
- for p in self.points])
|
|
|
|
|
- rotation = reduce(add, angles)
|
|
|
|
|
- pinch = reduce(add, pinches)
|
|
|
|
|
|
|
+ if self.angle == None:
|
|
|
|
|
+ self.set_state(angle, distance)
|
|
|
|
|
+ self.set_prev_state()
|
|
|
|
|
+ else:
|
|
|
|
|
+ self.set_prev_state()
|
|
|
|
|
+ self.set_state(angle, distance)
|
|
|
|
|
|
|
|
- if rotation:
|
|
|
|
|
- self.trigger(Rotate(cx, cy, rotation / l, l))
|
|
|
|
|
|
|
+ def set_state(self, angle, distance):
|
|
|
|
|
+ self.angle = angle
|
|
|
|
|
+ self.distance = distance
|
|
|
|
|
|
|
|
- if pinch:
|
|
|
|
|
- self.trigger(Pinch(cx, cy, pinch / l * 2, l))
|
|
|
|
|
|
|
+ def set_prev_state(self):
|
|
|
|
|
+ self.prev_angle = self.angle
|
|
|
|
|
+ self.prev_distance = self.distance
|
|
|
|
|
+
|
|
|
|
|
+ def state_diff(self):
|
|
|
|
|
+ da = self.angle - self.prev_angle
|
|
|
|
|
+
|
|
|
|
|
+ # Assert that angle is in [-pi, pi]
|
|
|
|
|
+ #if da > pi:
|
|
|
|
|
+ # da %= 2 * pi
|
|
|
|
|
+ #elif da < -pi:
|
|
|
|
|
+ # da %= -2 * pi
|
|
|
|
|
+
|
|
|
|
|
+ return da, self.distance / self.prev_distance
|
|
|
|
|
+
|
|
|
|
|
+ def update_centroid(self):
|
|
|
|
|
+ """
|
|
|
|
|
+ Calculate the centroid of all current touch points.
|
|
|
|
|
+ """
|
|
|
|
|
+ self.old_centroid = self.centroid
|
|
|
|
|
+ l = len(self.points)
|
|
|
|
|
+
|
|
|
|
|
+ # If there are no touch points, move the centroid to outside the screen
|
|
|
|
|
+ if not l:
|
|
|
|
|
+ self.centroid = -1., -1.
|
|
|
|
|
+ return
|
|
|
|
|
+
|
|
|
|
|
+ # Only use stationary points, if any
|
|
|
|
|
+ use = filter(TouchPoint.is_stationary, self.points)
|
|
|
|
|
+
|
|
|
|
|
+ if not len(use):
|
|
|
|
|
+ use = self.points
|
|
|
|
|
+
|
|
|
|
|
+ cx, cy = zip(*[(p.x, p.y) for p in use])
|
|
|
|
|
+ self.centroid = reduce(add, cx, 0) / l, reduce(add, cy, 0) / l
|
|
|
|
|
+
|
|
|
|
|
+ def centroid_movement(self):
|
|
|
|
|
+ cx, cy = self.centroid
|
|
|
|
|
+ ocx, ocy = self.old_centroid
|
|
|
|
|
+
|
|
|
|
|
+ return cx - ocx, cy - ocy
|
|
|
|
|
|
|
|
def detect_pan(self):
|
|
def detect_pan(self):
|
|
|
"""
|
|
"""
|
|
@@ -254,9 +284,9 @@ class MultiTouchListener(Logger):
|
|
|
return False
|
|
return False
|
|
|
|
|
|
|
|
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(*self.centroid) <= m \
|
|
|
for p in self.points])
|
|
for p in self.points])
|
|
|
- directions = [(cmp(p.dx(), 0), cmp(p.dy(), 0)) \
|
|
|
|
|
|
|
+ directions = [(cmp(p.dx(), 0), cmp(p.dy(), 0))
|
|
|
for p in self.points]
|
|
for p in self.points]
|
|
|
|
|
|
|
|
if not clustered or not any(map(all, zip(*directions))):
|
|
if not clustered or not any(map(all, zip(*directions))):
|
|
@@ -282,15 +312,6 @@ class MultiTouchListener(Logger):
|
|
|
if index:
|
|
if index:
|
|
|
return -1, None
|
|
return -1, None
|
|
|
|
|
|
|
|
- def detect_pinch(self, moved):
|
|
|
|
|
- cx, cy = self.centroid
|
|
|
|
|
- dist = moved.distance_to(cx, cy)
|
|
|
|
|
- old_dist = distance((moved.px, moved.py), self.centroid)
|
|
|
|
|
-
|
|
|
|
|
- if abs(dist - old_dist) > DIST_THRESHOLD:
|
|
|
|
|
- self.trigger(Pinch(cx, cy, dist / old_dist,
|
|
|
|
|
- len(self.points)))
|
|
|
|
|
-
|
|
|
|
|
def detect_single_tap(self):
|
|
def detect_single_tap(self):
|
|
|
"""
|
|
"""
|
|
|
Check if a single tap event should be triggered by checking is the last
|
|
Check if a single tap event should be triggered by checking is the last
|
|
@@ -301,31 +322,6 @@ class MultiTouchListener(Logger):
|
|
|
self.last_tap = None
|
|
self.last_tap = None
|
|
|
self.last_tap_time = 0
|
|
self.last_tap_time = 0
|
|
|
|
|
|
|
|
- def update_centroid(self):
|
|
|
|
|
- """
|
|
|
|
|
- Calculate the centroid of all current touch points.
|
|
|
|
|
- """
|
|
|
|
|
- self.old_centroid = self.centroid
|
|
|
|
|
- l = len(self.points)
|
|
|
|
|
-
|
|
|
|
|
- # If there are no touch points, move the entroid to outside the screen
|
|
|
|
|
- if not l:
|
|
|
|
|
- self.centroid = (-1., -1.)
|
|
|
|
|
- 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)
|
|
|
|
|
-
|
|
|
|
|
- # Update angle and pinch of all touch points
|
|
|
|
|
- for p in self.points:
|
|
|
|
|
- p.set_centroid(*self.centroid)
|
|
|
|
|
-
|
|
|
|
|
- def centroid_movement(self):
|
|
|
|
|
- cx, cy = self.centroid
|
|
|
|
|
- ocx, ocy = self.old_centroid
|
|
|
|
|
-
|
|
|
|
|
- return cx - ocx, cy - ocy
|
|
|
|
|
-
|
|
|
|
|
def detect_gestures(self):
|
|
def detect_gestures(self):
|
|
|
"""
|
|
"""
|
|
|
Detect if any gestures have occured in the past gesture frame. This
|
|
Detect if any gestures have occured in the past gesture frame. This
|
|
@@ -432,6 +428,7 @@ if __name__ == '__main__':
|
|
|
listener.bind('single_tap', tap, 1)
|
|
listener.bind('single_tap', tap, 1)
|
|
|
listener.bind('double_tap', tap, 2)
|
|
listener.bind('double_tap', tap, 2)
|
|
|
listener.bind('rotate', lambda e: 0)
|
|
listener.bind('rotate', lambda e: 0)
|
|
|
|
|
+ listener.bind('pinch', lambda e: 0)
|
|
|
|
|
|
|
|
try:
|
|
try:
|
|
|
listener.start()
|
|
listener.start()
|