diff options
Diffstat (limited to 'src/python/gesture.py')
-rw-r--r-- | src/python/gesture.py | 1000 |
1 files changed, 1000 insertions, 0 deletions
diff --git a/src/python/gesture.py b/src/python/gesture.py new file mode 100644 index 0000000..0334e26 --- /dev/null +++ b/src/python/gesture.py @@ -0,0 +1,1000 @@ +# -*- coding: utf-8 -*- +# libavg - Media Playback Engine. +# Copyright (C) 2003-2014 Ulrich von Zadow +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# +# Current versions can be found at www.libavg.de +# + +from libavg import avg, statemachine, player, filter + +import weakref + +import math + +class Recognizer(avg.Publisher): + + POSSIBLE = avg.Publisher.genMessageID() + DETECTED = avg.Publisher.genMessageID() + FAILED = avg.Publisher.genMessageID() + MOTION = avg.Publisher.genMessageID() + UP = avg.Publisher.genMessageID() + END = avg.Publisher.genMessageID() + + def __init__(self, node, isContinuous, maxContacts, initialEvent, + possibleHandler=None, failHandler=None, detectedHandler=None, + endHandler=None): + super(Recognizer, self).__init__() + + if node: + self.__node = weakref.ref(node) + else: + self.__node = None + self.__isContinuous = isContinuous + self.__maxContacts = maxContacts + + self.__setEventHandler() + self.__isEnabled = True + self._contacts = set() + self.__dirty = False + + self.publish(Recognizer.POSSIBLE) + self.publish(Recognizer.DETECTED) + self.publish(Recognizer.FAILED) + self.publish(Recognizer.END) + self.__stateMachine = statemachine.StateMachine(str(type(self)), "IDLE") + if self.__isContinuous: + self.publish(Recognizer.MOTION) + self.publish(Recognizer.UP) + self.__stateMachine.addState("IDLE", ("POSSIBLE", "RUNNING")) + self.__stateMachine.addState("POSSIBLE", ("IDLE", "RUNNING")) + self.__stateMachine.addState("RUNNING", ("IDLE",)) + else: + self.__stateMachine.addState("IDLE", ("POSSIBLE",)) + self.__stateMachine.addState("POSSIBLE", ("IDLE",)) + + self.subscribe(Recognizer.POSSIBLE, possibleHandler) + self.subscribe(Recognizer.FAILED, failHandler) + self.subscribe(Recognizer.DETECTED, detectedHandler) + self.subscribe(Recognizer.END, endHandler) + # self.__stateMachine.traceChanges(True) + self.__frameHandlerID = None + + if initialEvent: + self.__onDown(initialEvent) + + @property + def contacts(self): + return list(self._contacts) + + def abort(self): + if self.__isEnabled: + self.__abort() + self.__setEventHandler() + + def enable(self, isEnabled): + if bool(isEnabled) != self.__isEnabled: + self.__isEnabled = bool(isEnabled) + if isEnabled: + self.__setEventHandler() + else: + self.__abort() + + def isEnabled(self): + return self.__isEnabled + + def getState(self): + return self.__stateMachine.state + + def _setPossible(self, event): + self.__stateMachine.changeState("POSSIBLE") + self.notifySubscribers(Recognizer.POSSIBLE, []) + + def _setFail(self, event): + assert(self.__stateMachine.state != "RUNNING") + if self.__stateMachine.state != "IDLE": + self.__stateMachine.changeState("IDLE") + self._disconnectContacts() + self.notifySubscribers(Recognizer.FAILED, []) + + def _setDetected(self, event): + if self.__isContinuous: + self.__stateMachine.changeState("RUNNING") + else: + self.__stateMachine.changeState("IDLE") + self.notifySubscribers(Recognizer.DETECTED, []) + + def _setEnd(self, event): + assert(self.__stateMachine.state != "POSSIBLE") + if self.__stateMachine.state != "IDLE": + self.__stateMachine.changeState("IDLE") + self.notifySubscribers(Recognizer.END, []) + + def __onDown(self, event): + nodeGone = self._handleNodeGone() + if event.contact and not(nodeGone): + if (self.__maxContacts == None or len(self._contacts) < + self.__maxContacts): + event.contact.subscribe(avg.Contact.CURSOR_MOTION, self.__onMotion) + event.contact.subscribe(avg.Contact.CURSOR_UP, self.__onUp) + self._contacts.add(event.contact) + if len(self._contacts) == 1: + self.__frameHandlerID = player.subscribe(player.ON_FRAME, + self._onFrame) + self.__dirty = True + return self._handleDown(event) + + def __onMotion(self, event): + nodeGone = self._handleNodeGone() + if event.contact and not(nodeGone): + self.__dirty = True + self._handleMove(event) + + def __onUp(self, event): + nodeGone = self._handleNodeGone() + if event.contact and not(nodeGone): + self.__dirty = True + self._contacts.remove(event.contact) + if len(self._contacts) == 0: + player.unsubscribe(player.ON_FRAME, self.__frameHandlerID) + self.__frameHandlerID = None + self._handleUp(event) + + def __abort(self): + if self.__stateMachine.state != "IDLE": + self.__stateMachine.changeState("IDLE") + if len(self._contacts) != 0: + self._disconnectContacts() + if self.__node and self.__node(): + self.__node().unsubscribe(avg.Node.CURSOR_DOWN, self.__onDown) + + def _disconnectContacts(self): + for contact in self._contacts: + contact.unsubscribe(avg.Contact.CURSOR_MOTION, self.__onMotion) + contact.unsubscribe(avg.Contact.CURSOR_UP, self.__onUp) + self._contacts = set() + if self.__frameHandlerID: + player.unsubscribe(player.ON_FRAME, self.__frameHandlerID) + self.__frameHandlerID = None + + def _handleDown(self, event): + pass + + def _handleMove(self, event): + pass + + def _handleUp(self, event): + pass + + def _handleChange(self): + pass + + def _onFrame(self): + nodeGone = self._handleNodeGone() + if not(nodeGone) and self.__dirty: + self._handleChange() + self.__dirty = False + + def _handleNodeGone(self): + if self.__node and not(self.__node()): + self.enable(False) + return True + else: + return False + + def __setEventHandler(self): + if self.__node and self.__node(): + self.__node().subscribe(avg.Node.CURSOR_DOWN, self.__onDown) + + +class TapRecognizer(Recognizer): + + MAX_TAP_DIST = None + + def __init__(self, node, maxTime=None, maxDist=None, initialEvent=None, + possibleHandler=None, failHandler=None, detectedHandler=None): + self.__maxTime = maxTime + if maxDist == None: + maxDist = TapRecognizer.MAX_TAP_DIST + self.__maxDist = maxDist + super(TapRecognizer, self).__init__(node, False, 1, initialEvent, + possibleHandler, failHandler, detectedHandler) + + def _handleDown(self, event): + self._setPossible(event) + self.__startTime = player.getFrameTime() + + def _handleMove(self, event): + if self.getState() != "IDLE": + if (event.contact.distancefromstart > + self.__maxDist*player.getPixelsPerMM()): + self._setFail(event) + + def _handleUp(self, event): + if self.getState() == "POSSIBLE": + if (event.contact.distancefromstart > + self.__maxDist*player.getPixelsPerMM()): + self._setFail(event) + else: + self._setDetected(event) + + def _onFrame(self): + downTime = player.getFrameTime() - self.__startTime + if self.getState() == "POSSIBLE": + if self.__maxTime and downTime > self.__maxTime: + self._setFail(None) + super(TapRecognizer, self)._onFrame() + + +class DoubletapRecognizer(Recognizer): + + MAX_DOUBLETAP_TIME = None + + def __init__(self, node, maxTime=None, maxDist=None, initialEvent=None, + possibleHandler=None, failHandler=None, detectedHandler=None): + if maxTime == None: + maxTime = DoubletapRecognizer.MAX_DOUBLETAP_TIME + self.__maxTime = maxTime + if maxDist == None: + maxDist = TapRecognizer.MAX_TAP_DIST + self.__maxDist = maxDist + + self.__stateMachine = statemachine.StateMachine("DoubletapRecognizer", "IDLE") + self.__stateMachine.addState("IDLE", ("DOWN1",), enterFunc=self.__enterIdle) + self.__stateMachine.addState("DOWN1", ("UP1", "IDLE")) + self.__stateMachine.addState("UP1", ("DOWN2", "IDLE")) + self.__stateMachine.addState("DOWN2", ("IDLE",)) + #self.__stateMachine.traceChanges(True) + self.__frameHandlerID = None + super(DoubletapRecognizer, self).__init__(node, False, 1, + initialEvent, possibleHandler, failHandler, detectedHandler) + + def abort(self): + if self.__stateMachine.state != "IDLE": + self.__stateMachine.changeState("IDLE") + super(DoubletapRecognizer, self).abort() + + def enable(self, isEnabled): + if self.__stateMachine.state != "IDLE": + self.__stateMachine.changeState("IDLE") + super(DoubletapRecognizer, self).enable(isEnabled) + + def _handleDown(self, event): + self.__startTime = player.getFrameTime() + if self.__stateMachine.state == "IDLE": + self.__frameHandlerID = player.subscribe(player.ON_FRAME, self.__onFrame) + self.__stateMachine.changeState("DOWN1") + self.__startPos = event.pos + self._setPossible(event) + elif self.__stateMachine.state == "UP1": + if ((event.pos - self.__startPos).getNorm() > + self.__maxDist*player.getPixelsPerMM()): + self.__stateMachine.changeState("IDLE") + self._setFail(event) + else: + self.__stateMachine.changeState("DOWN2") + else: + assert(False), self.__stateMachine.state + + def _handleMove(self, event): + if self.__stateMachine.state != "IDLE": + if ((event.pos - self.__startPos).getNorm() > + self.__maxDist*player.getPixelsPerMM()): + self.__stateMachine.changeState("IDLE") + self._setFail(event) + + def _handleUp(self, event): + if self.__stateMachine.state == "DOWN1": + self.__startTime = player.getFrameTime() + self.__stateMachine.changeState("UP1") + elif self.__stateMachine.state == "DOWN2": + if ((event.pos - self.__startPos).getNorm() > + self.__maxDist*player.getPixelsPerMM()): + self._setFail(event) + else: + self._setDetected(event) + self.__stateMachine.changeState("IDLE") + elif self.__stateMachine.state == "IDLE": + pass + else: + assert(False), self.__stateMachine.state + + def __onFrame(self): + downTime = player.getFrameTime() - self.__startTime + if downTime > self.__maxTime: + self._setFail(None) + self.__stateMachine.changeState("IDLE") + + def __enterIdle(self): + player.unsubscribe(player.ON_FRAME, self.__frameHandlerID) + + +class SwipeRecognizer(Recognizer): + + LEFT = 1 + RIGHT = 2 + UP = 3 + DOWN = 4 + + SWIPE_DIRECTION_TOLERANCE = math.pi/8 + MIN_SWIPE_DIST = 50 + MAX_SWIPE_CONTACT_DIST = 100 + + def __init__(self, node, direction, numContacts=1, initialEvent=None, + directionTolerance=SWIPE_DIRECTION_TOLERANCE, minDist=MIN_SWIPE_DIST, + maxContactDist=MAX_SWIPE_CONTACT_DIST, + possibleHandler=None, failHandler=None, detectedHandler=None): + + self.__numContacts = numContacts + self.__angleWanted = self.__angleFromDirection(direction) + self.__directionTolerance = directionTolerance + self.__minDist = minDist*player.getPixelsPerMM() + self.__maxInterContactDist = maxContactDist*player.getPixelsPerMM() + super(SwipeRecognizer, self).__init__(node, False, numContacts, + initialEvent, possibleHandler=possibleHandler, failHandler=failHandler, + detectedHandler=detectedHandler) + + def _handleDown(self, event): + if len(self._contacts) == 1: + self.__startPos = event.pos + else: + if (event.pos-self.__startPos).getNorm() > self.__maxInterContactDist: + self._setFail(event) + return + if len(self._contacts) == self.__numContacts: + self._setPossible(event) + + def _handleMove(self, event): + pass + + def _handleUp(self, event): + if self.getState() == "POSSIBLE": + if (event.contact.distancefromstart < self.__minDist or + not(self.__isValidAngle(event.contact.motionangle))): + self._setFail(event) + elif len(self._contacts) == 0: + self._setDetected(event) + + def __angleFromDirection(self, direction): + if direction == SwipeRecognizer.RIGHT: + return 0 + elif direction == SwipeRecognizer.DOWN: + return math.pi/2 + elif direction == SwipeRecognizer.LEFT: + return math.pi + elif direction == SwipeRecognizer.UP: + return 3*math.pi/2 + else: + raise RuntimeError("%s is not a valid direction."%direction) + + def __isValidAngle(self, angle): + if angle < 0: + angle += 2*math.pi + minAngle = self.__angleWanted - self.__directionTolerance + maxAngle = self.__angleWanted + self.__directionTolerance + if minAngle >= 0: + return angle > minAngle and angle < maxAngle + else: + # Valid range spans 0 + return angle > minAngle+2*math.pi or angle < maxAngle + + +class HoldRecognizer(Recognizer): + + HOLD_DELAY = None + + def __init__(self, node, delay=None, maxDist=None, initialEvent=None, + possibleHandler=None, failHandler=None, + detectedHandler=None, stopHandler=None): + if delay == None: + delay = HoldRecognizer.HOLD_DELAY + self.__delay = delay + if maxDist == None: + maxDist = TapRecognizer.MAX_TAP_DIST + self.__maxDist = maxDist + + self.__lastEvent = None + super(HoldRecognizer, self).__init__(node, True, 1, initialEvent, + possibleHandler, failHandler, detectedHandler, stopHandler) + + def _handleDown(self, event): + self.__lastEvent = event + self._setPossible(event) + self.__startTime = player.getFrameTime() + + def _handleMove(self, event): + self.__lastEvent = event + if self.getState() == "POSSIBLE": + if (event.contact.distancefromstart > + self.__maxDist*player.getPixelsPerMM()): + self._setFail(event) + + def _handleUp(self, event): + self.__lastEvent = event + if self.getState() == "POSSIBLE": + self._setFail(event) + elif self.getState() == "RUNNING": + self._setEnd(event) + + def _onFrame(self): + downTime = player.getFrameTime() - self.__startTime + if self.getState() == "POSSIBLE": + if downTime > self.__delay: + self._setDetected(self.__lastEvent) + super(HoldRecognizer, self)._onFrame() + + +class DragRecognizer(Recognizer): + + ANY_DIRECTION = 0 + VERTICAL = 1 + HORIZONTAL = 2 + + DIRECTION_TOLERANCE = math.pi/4 + MIN_DRAG_DIST = None + FRICTION = None + + def __init__(self, eventNode, coordSysNode=None, initialEvent=None, + direction=ANY_DIRECTION, directionTolerance=DIRECTION_TOLERANCE, + friction=None, minDragDist=None, + possibleHandler=None, failHandler=None, detectedHandler=None, + moveHandler=None, upHandler=None, endHandler=None): + + if coordSysNode != None: + self.__coordSysNode = weakref.ref(coordSysNode) + else: + self.__coordSysNode = weakref.ref(eventNode) + self.__direction = direction + self.__directionTolerance = directionTolerance + + if minDragDist != None: + self.__minDragDist = minDragDist + else: + if self.__direction == DragRecognizer.ANY_DIRECTION: + self.__minDragDist = 0 + else: + self.__minDragDist = DragRecognizer.MIN_DRAG_DIST + + if friction == None: + self.__friction = DragRecognizer.FRICTION + else: + self.__friction = friction + + self.__isSliding = False + self.__inertiaHandler = None + super(DragRecognizer, self).__init__(eventNode, True, 1, + initialEvent, possibleHandler=possibleHandler, failHandler=failHandler, + detectedHandler=detectedHandler, endHandler=endHandler) + self.subscribe(Recognizer.MOTION, moveHandler) + self.subscribe(Recognizer.UP, upHandler) + + def abort(self): + if self.__inertiaHandler: + self.__inertiaHandler.abort() + self.__inertiaHandler = None + super(DragRecognizer, self).abort() + + def _handleDown(self, event): + if not self._handleCoordSysNodeUnlinked(): + if self.__inertiaHandler: + self.__inertiaHandler.abort() + self._setEnd(event) + if self.__friction != -1: + self.__inertiaHandler = InertiaHandler(self.__friction, + self.__onInertiaMove, self.__onInertiaStop) + if self.__minDragDist == 0: + self._setDetected(event) + else: + self._setPossible(event) + pos = self.__relEventPos(event) + self.__dragStartPos = pos + self.__lastPos = pos + + def _handleMove(self, event): + if not self._handleCoordSysNodeUnlinked(): + if self.getState() != "IDLE": + pos = self.__relEventPos(event) + offset = pos - self.__dragStartPos + if self.getState() == "RUNNING": + self.notifySubscribers(Recognizer.MOTION, [offset]); + else: + if offset.getNorm() > self.__minDragDist*player.getPixelsPerMM(): + if self.__angleFits(offset): + self._setDetected(event) + self.notifySubscribers(Recognizer.MOTION, [offset]); + else: + self.__fail(event) + if self.__inertiaHandler: + self.__inertiaHandler.onDrag(Transform(pos - self.__lastPos)) + self.__lastPos = pos + + def _handleUp(self, event): + if not self._handleCoordSysNodeUnlinked(): + if self.getState() != "IDLE": + pos = self.__relEventPos(event) + if self.getState() == "RUNNING": + self.__offset = pos - self.__dragStartPos + self.notifySubscribers(Recognizer.UP, [self.__offset]); + if self.__friction != -1: + self.__isSliding = True + self.__inertiaHandler.onDrag(Transform(pos - self.__lastPos)) + self.__inertiaHandler.onUp() + else: + self._setEnd(event) + else: + self.__fail(event) + + def _handleCoordSysNodeUnlinked(self): + if self.__coordSysNode().getParent(): + return False + else: + self.abort() + return True + + def __fail(self, event): + self._setFail(event) + if self.__inertiaHandler: + self.__inertiaHandler.abort() + self.__inertiaHandler = None + + def __onInertiaMove(self, transform): + self.__offset += transform.trans + self.notifySubscribers(Recognizer.MOTION, [self.__offset]); + + def __onInertiaStop(self): + self.__inertiaHandler = None + self.__isSliding = False + if self.getState() == "POSSIBLE": + self._setFail(None) + else: + self._setEnd(None) + + def __relEventPos(self, event): + return self.__coordSysNode().getParent().getRelPos(event.pos) + + def __angleFits(self, offset): + angle = offset.getAngle() + if angle < 0: + angle = -angle + if self.__direction == DragRecognizer.VERTICAL: + return (angle > math.pi/2-self.__directionTolerance + and angle < math.pi/2+self.__directionTolerance) + elif self.__direction == DragRecognizer.HORIZONTAL: + return (angle < self.__directionTolerance + or angle > math.pi-self.__directionTolerance) + else: + return True + +class Mat3x3: + # Internal class. Will be removed again. + + def __init__(self, row0=(1,0,0), row1=(0,1,0), row2=(0,0,1)): + self.m = [row0, row1, row2] + + @classmethod + def translate(cls, t): + return Mat3x3([1, 0, t[0]], + [0, 1, t[1]]) + + @classmethod + def rotate(cls, a): + return Mat3x3([math.cos(a), -math.sin(a), 0], + [math.sin(a), math.cos(a), 0]) + + @classmethod + def pivotRotate(cls, t, a): + rot = Mat3x3.rotate(a) + trans = Mat3x3.translate(t) + return trans.applyMat(rot.applyMat(trans.inverse())) + + @classmethod + def scale(cls, s): + return Mat3x3([s[0], 0, 0], + [0, s[1], 0]) + + @classmethod + def fromNode(cls, node): + return Mat3x3.translate(node.pos).applyMat( + Mat3x3.translate(node.pivot).applyMat( + Mat3x3.rotate(node.angle).applyMat( + Mat3x3.translate(-node.pivot).applyMat( + Mat3x3.scale(node.size))))) + + def setNodeTransform(self, node): + v = self.applyVec([1,0,0]) + rot = avg.Point2D(v[0], v[1]).getAngle() + node.angle = rot + if self.getScale().x < 9999 and self.getScale().y < 9999: + node.size = self.getScale() + else: + node.size = (0,0) + node.pivot = node.size/2 + v = self.applyVec([0,0,1]) + node.pos = (avg.Point2D(v[0], v[1]) + (node.pivot).getRotated(node.angle) - + node.pivot) + + def getScale(self): + v = self.applyVec([1,0,0]) + xscale = avg.Point2D(v[0], v[1]).getNorm() + v = self.applyVec([0,1,0]) + yscale = avg.Point2D(v[0], v[1]).getNorm() + return avg.Point2D(xscale, yscale) + + def __str__(self): + return self.m.__str__() + + def applyVec(self, v): + m = self.m + v1 = [] + for i in range(3): + v1.append(m[i][0]*v[0] + m[i][1]*v[1] + m[i][2]*v[2]) + return v1 + + def applyMat(self, m1): + m0 = self.m + result = Mat3x3() + for i in range(3): + v = [] + for j in range(3): + v.append(m0[i][0]*m1.m[0][j] + m0[i][1]*m1.m[1][j] + m0[i][2]*m1.m[2][j]) + result.m[i] = v + return result + + def det(self): + m = self.m + return float( m[0][0] * (m[2][2]*m[1][1]-m[2][1]*m[1][2]) + -m[1][0] * (m[2][2]*m[0][1]-m[2][1]*m[0][2]) + +m[2][0] * (m[1][2]*m[0][1]-m[1][1]*m[0][2])) + + def scalarMult(self, s): + m = self.m + result = Mat3x3() + for i in range(3): + v = [] + for j in range(3): + v.append(m[i][j]*s) + result.m[i] = v + return result + + def inverse(self): + m = self.m + temp = Mat3x3([ m[2][2]*m[1][1]-m[2][1]*m[1][2], -(m[2][2]*m[0][1]-m[2][1]*m[0][2]), m[1][2]*m[0][1]-m[1][1]*m[0][2] ], + [-(m[2][2]*m[1][0]-m[2][0]*m[1][2]), m[2][2]*m[0][0]-m[2][0]*m[0][2] , -(m[1][2]*m[0][0]-m[1][0]*m[0][2])], + [ m[2][1]*m[1][0]-m[2][0]*m[1][1], -(m[2][1]*m[0][0]-m[2][0]*m[0][1]), m[1][1]*m[0][0]-m[1][0]*m[0][1] ]) + return temp.scalarMult(1/self.det()) + + +def getCentroid(indexes, pts): + c = avg.Point2D(0, 0) + for i in indexes: + c += pts[i] + return c/len(indexes) + +def calcKMeans(pts): + + # in: List of points + # out: Two lists, each containing indexes into the input list + assert(len(pts) > 1) + p1 = pts[0] + p2 = pts[1] + oldP1 = None + oldP2 = None + j = 0 + while not(p1 == oldP1 and p2 == oldP2) and j < 50: + l1 = [] + l2 = [] + # Group points + for i, pt in enumerate(pts): + dist1 = (pt-p1).getNorm() + dist2 = (pt-p2).getNorm() + if dist1 < dist2: + l1.append(i) + else: + l2.append(i) + oldP1 = p1 + oldP2 = p2 + p1 = getCentroid(l1, pts) + p2 = getCentroid(l2, pts) + j += 1 + return l1, l2 + + +class Transform(): + def __init__(self, trans, rot=0, scale=1, pivot=(0,0)): + self.trans = avg.Point2D(trans) + self.rot = rot + self.scale = scale + self.pivot = avg.Point2D(pivot) + + def moveNode(self, node): + transMat = Mat3x3.translate(self.trans) + rotMat = Mat3x3.rotate(self.rot) + scaleMat = Mat3x3.scale((self.scale, self.scale)) + pivotMat = Mat3x3.translate(self.pivot) + invPivotMat = pivotMat.inverse() + startTransform = Mat3x3.fromNode(node) + newTransform = pivotMat.applyMat( + rotMat.applyMat( + scaleMat.applyMat( + invPivotMat.applyMat( + transMat.applyMat( + startTransform))))) + newTransform.setNodeTransform(node) + + def __repr__(self): + return "Transform"+str((self.trans, self.rot, self.scale, self.pivot)) + + +class TransformRecognizer(Recognizer): + + FILTER_MIN_CUTOFF = None + FILTER_BETA = None + + def __init__(self, eventNode, coordSysNode=None, initialEvent=None, friction=None, + detectedHandler=None, moveHandler=None, upHandler=None, endHandler=None): + if coordSysNode != None: + self.__coordSysNode = weakref.ref(coordSysNode) + else: + self.__coordSysNode = weakref.ref(eventNode) + + if friction == None: + self.__friction = DragRecognizer.FRICTION + else: + self.__friction = friction + + self.__baseTransform = Mat3x3() + self.__lastPosns = [] + self.__posns = [] + self.__inertiaHandler = None + self.__filters = {} + self.__frameHandlerID = None + super(TransformRecognizer, self).__init__(eventNode, True, None, + initialEvent, detectedHandler=detectedHandler, endHandler=endHandler) + self.subscribe(Recognizer.MOTION, moveHandler) + self.subscribe(Recognizer.UP, upHandler) + + def enable(self, isEnabled): + if bool(isEnabled) != self.isEnabled() and not(isEnabled): + self.__abort() + super(TransformRecognizer, self).enable(isEnabled) + + def abort(self): + self.__abort() + super(TransformRecognizer, self).abort() + + def _handleDown(self, event): + numContacts = len(self._contacts) + self.__newPhase() + if self.__isFiltered(): + self.__filters[event.contact] = [ + filter.OneEuroFilter(mincutoff=TransformRecognizer.FILTER_MIN_CUTOFF, + beta=TransformRecognizer.FILTER_BETA), + filter.OneEuroFilter(mincutoff=TransformRecognizer.FILTER_MIN_CUTOFF, + beta=TransformRecognizer.FILTER_BETA)] + if numContacts == 1: + if self.__inertiaHandler: + self.__inertiaHandler.abort() + self._setEnd(event) + self._setDetected(event) + self.__frameHandlerID = player.subscribe(player.ON_FRAME, self.__onFrame) + if self.__friction != -1: + self.__inertiaHandler = InertiaHandler(self.__friction, + self.__onInertiaMove, self.__onInertiaStop) + + def _handleUp(self, event): + numContacts = len(self._contacts) + if numContacts == 0: + contact = event.contact + transform = Transform(self.__filteredRelContactPos(contact) + - self.__lastPosns[0]) + self.notifySubscribers(Recognizer.UP, [transform]); + player.unsubscribe(player.ON_FRAME, self.__frameHandlerID) + self.__frameHandlerID = None + if self.__friction != -1: + self.__inertiaHandler.onDrag(transform) + self.__inertiaHandler.onUp() + else: + self._setEnd(event) + elif numContacts == 1: + self.__newPhase() + else: + self.__newPhase() + if self.__isFiltered(): + del self.__filters[event.contact] + + def _handleNodeGone(self): + if self.__coordSysNode and not(self.__coordSysNode()): + self.enable(False) + return True + else: + return super(TransformRecognizer, self)._handleNodeGone() + + def __onFrame(self): + nodeGone = self._handleNodeGone() + if not(nodeGone): + self.__move() + + def __move(self): + numContacts = len(self._contacts) + contactPosns = [self.__filteredRelContactPos(contact) + for contact in self._contacts] + if numContacts == 1: + transform = Transform(contactPosns[0] - self.__lastPosns[0]) + if self.__friction != -1: + self.__inertiaHandler.onDrag(transform) + self.notifySubscribers(Recognizer.MOTION, [transform]); + self.__lastPosns = contactPosns + else: + if numContacts == 2: + self.__posns = contactPosns + else: + self.__posns = [getCentroid(self.__clusters[i], contactPosns) for + i in range(2)] + + startDelta = self.__lastPosns[1]-self.__lastPosns[0] + curDelta = self.__posns[1]-self.__posns[0] + + pivot = (self.__posns[0]+self.__posns[1])/2 + + rot = avg.Point2D.angle(curDelta, startDelta) + + if self.__lastPosns[0] == self.__lastPosns[1]: + scale = 1 + else: + scale = ((self.__posns[0]-self.__posns[1]).getNorm() / + (self.__lastPosns[0]-self.__lastPosns[1]).getNorm()) + + trans = ((self.__posns[0]+self.__posns[1])/2 - + (self.__lastPosns[0]+self.__lastPosns[1])/2) + transform = Transform(trans, rot, scale, pivot) + if self.__friction != -1: + self.__inertiaHandler.onDrag(transform) + self.notifySubscribers(Recognizer.MOTION, [transform]); + self.__lastPosns = self.__posns + + def __newPhase(self): + self.__lastPosns = [] + numContacts = len(self._contacts) + contactPosns = [self.__relContactPos(contact) + for contact in self._contacts] + if numContacts == 1: + self.__lastPosns.append(contactPosns[0]) + else: + if numContacts == 2: + self.__lastPosns = contactPosns + else: + self.__clusters = calcKMeans(contactPosns) + self.__lastPosns = [getCentroid(self.__clusters[i], contactPosns) for + i in range(2)] + + def __onInertiaMove(self, transform): + self.notifySubscribers(Recognizer.MOTION, [transform]); + + def __onInertiaStop(self): + self.__inertiaHandler = None + self._setEnd(None) + + def __filteredRelContactPos(self, contact): + rawPos = self.__relContactPos(contact) + if self.__isFiltered(): + f = self.__filters[contact] + return avg.Point2D(f[0].apply(rawPos.x, player.getFrameTime()), + f[1].apply(rawPos.y, player.getFrameTime())) + else: + return rawPos + + def __relContactPos(self, contact): + return self.__coordSysNode().getParent().getRelPos(contact.events[-1].pos) + + def __isFiltered(self): + return TransformRecognizer.FILTER_MIN_CUTOFF != None + + def __abort(self): + if self.__frameHandlerID: + player.unsubscribe(player.ON_FRAME, self.__frameHandlerID) + self.__frameHandlerID = None + if self.__inertiaHandler: + self.__inertiaHandler.abort() + self.__inertiaHandler = None + + +class InertiaHandler(object): + def __init__(self, friction, moveHandler, stopHandler): + self.__friction = friction + self.__moveHandler = moveHandler + self.__stopHandler = stopHandler + + self.__transVel = avg.Point2D(0, 0) + self.__curPivot = avg.Point2D(0, 0) + self.__angVel = 0 + self.__sizeVel = avg.Point2D(0, 0) + self.__frameHandlerID = player.subscribe(player.ON_FRAME, self.__onDragFrame) + + def abort(self): + player.unsubscribe(player.ON_FRAME, self.__frameHandlerID) + self.__stopHandler = None + self.__moveHandler = None + + def onDrag(self, transform): + frameDuration = player.getFrameDuration() + if frameDuration > 0: + self.__transVel += 0.1*transform.trans/frameDuration + if transform.pivot != avg.Point2D(0,0): + self.__curPivot = transform.pivot + if transform.rot > math.pi: + transform.rot -= 2*math.pi + if frameDuration > 0: + self.__angVel += 0.1*transform.rot/frameDuration + + def onUp(self): + player.unsubscribe(player.ON_FRAME, self.__frameHandlerID) + self.__frameHandlerID = player.subscribe(player.ON_FRAME, self.__onInertiaFrame) + self.__onInertiaFrame() + + def __onDragFrame(self): + self.__transVel *= 0.9 + self.__angVel *= 0.9 + + def __onInertiaFrame(self): + transNorm = self.__transVel.getNorm() + if transNorm - self.__friction > 0: + direction = self.__transVel.getNormalized() + self.__transVel = direction * (transNorm-self.__friction) + curTrans = self.__transVel * player.getFrameDuration() + else: + curTrans = avg.Point2D(0, 0) + + if self.__angVel != 0: + angSign = self.__angVel/math.fabs(self.__angVel) + self.__angVel = self.__angVel - angSign*self.__friction/200 + newAngSign = self.__angVel/math.fabs(self.__angVel) + if newAngSign != angSign: + self.__angVel = 0 + curAng = self.__angVel * player.getFrameDuration() + self.__curPivot += curTrans + + if transNorm - self.__friction > 0 or self.__angVel != 0: + if self.__moveHandler: + self.__moveHandler(Transform(curTrans, curAng, 1, self.__curPivot)) + else: + self.__stop() + + def __stop(self): + player.unsubscribe(player.ON_FRAME, self.__frameHandlerID) + self.__stopHandler() + self.__stopHandler = None + self.__moveHandler = None + + +def initConfig(): + def getFloatOption(name): + return float(player.getConfigOption("gesture", name)) + + TapRecognizer.MAX_TAP_DIST = getFloatOption("maxtapdist") + DoubletapRecognizer.MAX_DOUBLETAP_TIME = getFloatOption("maxdoubletaptime") + SwipeRecognizer.MIN_SWIPE_DIST = getFloatOption("minswipedist") + SwipeRecognizer.SWIPE_DIRECTION_TOLERANCE = getFloatOption("swipedirectiontolerance") + SwipeRecognizer.MAX_SWIPE_CONTACT_DIST = getFloatOption("maxswipecontactdist") + HoldRecognizer.HOLD_DELAY = getFloatOption("holddelay") + DragRecognizer.MIN_DRAG_DIST = getFloatOption("mindragdist") + DragRecognizer.FRICTION = getFloatOption("friction") + TransformRecognizer.FILTER_MIN_CUTOFF = getFloatOption("filtermincutoff") + if TransformRecognizer.FILTER_MIN_CUTOFF == -1: + TransformRecognizer.FILTER_MIN_CUTOFF = None + TransformRecognizer.FILTER_BETA = getFloatOption("filterbeta") + + +initConfig() |