Explorar el Código

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

Taddeus Kroes hace 13 años
padre
commit
a96fdaa12b
Se han modificado 3 ficheros con 190 adiciones y 8 borrados
  1. 26 8
      src/geometry.py
  2. 117 0
      src/trackers/transform.py
  3. 47 0
      tests/transform.py

+ 26 - 8
src/geometry.py

@@ -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):
     """

+ 117 - 0
src/trackers/transform.py

@@ -0,0 +1,117 @@
+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())

+ 47 - 0
tests/transform.py

@@ -0,0 +1,47 @@
+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()