Skip to content
Snippets Groups Projects
Commit a96fdaa1 authored by Taddeus Kroes's avatar Taddeus Kroes
Browse files

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

parent 0032d621
No related branches found
No related tags found
No related merge requests found
......@@ -43,8 +43,7 @@ class MovingPositionable(Positionable):
"""
def __init__(self, x=None, y=None):
super(MovingPositionable, self).__init__(x, y)
self.px = x
self.py = y
self.prev = Positionable(x, y)
def set_position(self, x, y):
"""
......@@ -53,16 +52,14 @@ class MovingPositionable(Positionable):
position so that the movement is zero.
"""
if self.x is None or self.y is None:
self.px = x
self.py = y
self.prev.set_position(x, y)
else:
self.px = self.x
self.py = self.y
self.prev.set_position(self.x, self.y)
Positionable.set_position(self, x, y)
def get_previous_position(self):
return self.px, self.py
return self.prev
def rotation_around(self, center):
"""
......@@ -70,7 +67,8 @@ class MovingPositionable(Positionable):
positionable.
"""
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)
rotation = current_angle - prev_angle
......@@ -82,6 +80,17 @@ class MovingPositionable(Positionable):
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):
"""
......@@ -103,6 +112,15 @@ class AcceleratedPositionable(MovingPositionable):
self.prev_timestamp = self.current_timestamp
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):
"""
......
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()
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment