Commit a96fdaa1 authored by Taddeus Kroes's avatar Taddeus Kroes

Added tracker for transformations (rotate, pinch, movement).

parent 0032d621
...@@ -43,8 +43,7 @@ class MovingPositionable(Positionable): ...@@ -43,8 +43,7 @@ class MovingPositionable(Positionable):
""" """
def __init__(self, x=None, y=None): def __init__(self, x=None, y=None):
super(MovingPositionable, self).__init__(x, y) super(MovingPositionable, self).__init__(x, y)
self.px = x self.prev = Positionable(x, y)
self.py = y
def set_position(self, x, y): def set_position(self, x, y):
""" """
...@@ -53,16 +52,14 @@ class MovingPositionable(Positionable): ...@@ -53,16 +52,14 @@ class MovingPositionable(Positionable):
position so that the movement is zero. position so that the movement is zero.
""" """
if self.x is None or self.y is None: if self.x is None or self.y is None:
self.px = x self.prev.set_position(x, y)
self.py = y
else: else:
self.px = self.x self.prev.set_position(self.x, self.y)
self.py = self.y
Positionable.set_position(self, x, y) Positionable.set_position(self, x, y)
def get_previous_position(self): def get_previous_position(self):
return self.px, self.py return self.prev
def rotation_around(self, center): def rotation_around(self, center):
""" """
...@@ -70,7 +67,8 @@ class MovingPositionable(Positionable): ...@@ -70,7 +67,8 @@ class MovingPositionable(Positionable):
positionable. positionable.
""" """
cx, cy = center.get_position() cx, cy = center.get_position()
prev_angle = atan2(self.px - cx, self.py - cy) px, py = self.prev.get_position()
prev_angle = atan2(px - cx, py - cy)
current_angle = atan2(self.x - cx, self.y - cy) current_angle = atan2(self.x - cx, self.y - cy)
rotation = current_angle - prev_angle rotation = current_angle - prev_angle
...@@ -82,6 +80,17 @@ class MovingPositionable(Positionable): ...@@ -82,6 +80,17 @@ class MovingPositionable(Positionable):
return rotation return rotation
def translation(self):
"""
Calculate the movement relative to the last position as a vector
positionable.
"""
px, py = self.prev.get_position()
return Positionable(self.x - px, self.y - py)
def movement_distance(self):
return self.distance_to(self.prev)
class AcceleratedPositionable(MovingPositionable): class AcceleratedPositionable(MovingPositionable):
""" """
...@@ -103,6 +112,15 @@ class AcceleratedPositionable(MovingPositionable): ...@@ -103,6 +112,15 @@ class AcceleratedPositionable(MovingPositionable):
self.prev_timestamp = self.current_timestamp self.prev_timestamp = self.current_timestamp
self.current_timestamp = time.time() self.current_timestamp = time.time()
def movement_time(self):
return self.timestamp - self.prev_timestamp
def acceleration(self):
"""
Calculate the acceleration in pixels/second.
"""
return self.movement_distance() / self.movement_time()
class Surface(Positionable): class Surface(Positionable):
""" """
......
from __future__ import division
from ..tracker import GestureTracker, Gesture
from ..geometry import Positionable, MovingPositionable
class TransformationTracker(GestureTracker):
"""
Tracker for linear transformations. This implementation detects rotation,
scaling and translation using the centroid of all touch points.
"""
__gesture_types__ = ['rotate', 'pinch', 'move']
def __init__(self, window=None):
super(TransformationTracker, self).__init__(window)
# All touch points performing the transformation
self.points = []
# Current and previous centroid of all touch points
self.prev_centroid = self.centroid = None
def update_centroid(self):
self.prev_centroid = self.centroid
if not self.points:
self.centroid = None
return
# Calculate average touch point coordinates
l = len(self.points)
coords = [p.get_position() for p in self.points]
all_x, all_y = zip(*coords)
x = sum(all_x) / l
y = sum(all_y) / l
# Update centroid positionable
if self.centroid:
self.centroid.set_position(x, y)
else:
self.centroid = MovingPositionable(x, y)
def on_point_down(self, point):
self.points.append(point)
self.update_centroid()
def on_point_move(self, point):
if len(self.points) > 1:
# Rotation (around the previous centroid)
if self.is_type_bound('rotate'):
rotation = point.rotation_around(self.centroid)
self.trigger(RotationGesture(self.centroid, rotation))
# Scale
if self.is_type_bound('pinch'):
prev = point.get_previous_position().distance_to(self.centroid)
dist = point.distance_to(self.centroid)
scale = dist / prev
self.trigger(PinchGesture(self.centroid, scale))
# Update centroid before movement can be detected
self.update_centroid()
# Movement
self.trigger(MovementGesture(self.centroid,
self.centroid.translation()))
def on_point_up(self, point):
self.points.remove(point)
self.update_centroid()
class RotationGesture(Positionable, Gesture):
"""
A rotation gesture has a angle in radians and a rotational centroid.
"""
__type__ = 'rotate'
def __init__(self, centroid, angle):
Positionable.__init__(self, *centroid.get_position())
self.angle = angle
def __str__(self):
return '<RotationGesture at (%s, %s) angle=%s>' \
% (self.x, self.y, self.angle)
class PinchGesture(Positionable, Gesture):
"""
A pinch gesture has a scale (1.0 means no scaling) and a centroid from
which the scaling originates.
"""
__type__ = 'pinch'
def __init__(self, centroid, scale):
Positionable.__init__(self, *centroid.get_position())
self.scale = scale
def __str__(self):
return '<PinchGesture at (%s, %s) scale=%s>' \
% (self.x, self.y, self.scale)
class MovementGesture(Positionable, Gesture):
"""
A momevent gesture has an initial position, and a translation from that
position.
"""
__type__ = 'move'
def __init__(self, initial_position, translation):
Positionable.__init__(self, *initial_position.get_position())
self.translation = translation
def __str__(self):
return '<MovementGesture at (%s, %s) translation=(%s, %s)>' \
% (self.get_position() + self.translation.get_position())
import argparse
import logging
from src.server import GestureServer
from src.window import FullscreenWindow
from src.trackers.transform import TransformationTracker
from src.logger import Logger
# Parse arguments
parser = argparse.ArgumentParser(description='Basic test program for usage '
'of multi-touch API.')
parser.add_argument('--log', metavar='LOG_LEVEL', default='INFO',
choices=['DEBUG', 'INFO', 'WARNING'],
help='set log level (defaults to INFO)')
parser.add_argument('--logfile', metavar='FILENAME',
help='filename for the log file (the log is printed to '
'stdout by default)')
args = parser.parse_args()
# Configure logger
log_config = {'level': getattr(logging, args.log)}
if args.logfile:
log_config['filename'] = args.logfile
Logger.configure(**log_config)
# Create server
server = GestureServer()
# Create a window to add trackers to
win = FullscreenWindow(server=server)
# Add tracker and handlers
tracker = TransformationTracker(win)
tracker.bind('rotate', lambda g: 0)
tracker.bind('pinch', lambda g: 0)
tracker.bind('move', lambda g: 0)
# Start listening to TUIO events
try:
server.start()
except KeyboardInterrupt:
server.stop()
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