• Tutorials
  • Blog
  • About
  • Contact
  • Rotatable Scalable (Part 2)

    2010 - 02.13

    In this video screencast we suffer through the lengthy addition of adding TouchEvents to our RotatableScalable class for multitouch use with action script display objects. We are largely copying and pasting code over from the orginal RotatableScalable class which you can find at the TouchLib open source project home. Our finished class is still far from perfect, but you can copy the current version which is following the video.

    RotatableScalable2

    RotatableScalable:

    
    package com.mlegrand
    {
     import flash.display.DisplayObject;
     import flash.events.Event;
     import flash.events.TouchEvent;
     import flash.events.TransformGestureEvent;
     import flash.geom.Matrix;
     import flash.geom.Point;
    
     ///////////////////////////////////////////////////////////////////////////////////////
     //
     //  Original class can be found at :
     //  http://touchlib.googlecode.com/svn/trunk/AS3/int/app/core/action/RotatableScalable.as
     //
     //////////////////////////////////////////////////////////////////////////////////////
    
     public class RotatableScalable
     {
     //States
     protected static const NONE:String = 'none';
     protected static const DRAGGING:String = 'dragging';
     protected static const ROTATE_SCALE:String = 'rotateScale';
    
     //
     protected static const GRAD_PI:Number = 180/3.14159;
     protected static const HALF:Number = 0.5;
    
     protected var displayO:DisplayObject;
    
     protected var blobs : Array = [];
     protected var blob1 : Object;
     protected var blob2 : Object;
     protected var pointMap : Object = {};
    
     protected var state:String;
     protected var curScale:Number;
     protected var curAngle:Number;
     protected var curPosition:Point = new Point(0,0);
    
     protected var point1:Object;
     protected var point2:Object;
    
     protected var dX:Number;
     protected var dY:Number;
     protected var dAng:Number;
    
     protected var _rp:Point = new Point();
    
     //
     // Properties
     //
     public var draggable:Boolean;
    
     public function RotatableScalable(displayObject:DisplayObject)
     {
     displayO = displayObject;
     state = NONE;
     blobs = new Array();
     dX = 0;
     dY = 0;
     dAng = 0;
     addGestureEventListeners();
     addTouchEventListeners();
     }
    
     //
     //  Event Listeners
     //
    
     protected function addGestureEventListeners():void
     {
     displayO.addEventListener(TransformGestureEvent.GESTURE_ROTATE,
     onGestureRotate);
     displayO.addEventListener(TransformGestureEvent.GESTURE_ZOOM,
     onGesturePinch);
     }
    
     protected function addTouchEventListeners():void
     {
     displayO.addEventListener( TouchEvent.TOUCH_BEGIN, onTouchBegin );
     displayO.addEventListener( TouchEvent.TOUCH_END, onTouchEnd );
     displayO.addEventListener( TouchEvent.TOUCH_MOVE, onTouchMove );
     displayO.addEventListener( Event.ENTER_FRAME, update );
    
     displayO.addEventListener(TransformGestureEvent.GESTURE_ROTATE,
     onGestureRotate);
     displayO.addEventListener(TransformGestureEvent.GESTURE_ZOOM,
     onGesturePinch);
     }
    
     //
     //  Gesture Event Handlers
     //
    
     private function onGesturePinch(pinchEvent:TransformGestureEvent):void{
    
     pinchEvent.stopImmediatePropagation()
     var pinchMatrix:Matrix = displayO.transform.matrix;
     var pinchPoint:Point =
     pinchMatrix.transformPoint(
     new Point((displayO.width/2), (displayO.height/2)));
     pinchMatrix.translate(-pinchPoint.x, -pinchPoint.y);
     pinchMatrix.scale(pinchEvent.scaleX, pinchEvent.scaleY);
     pinchMatrix.translate(pinchPoint.x, pinchPoint.y);
     displayO.transform.matrix = pinchMatrix;
     }
    
     private function onGestureRotate(rotateEvent:TransformGestureEvent):void
     {
     rotateEvent.stopImmediatePropagation()
     var rotateMatrix:Matrix = displayO.transform.matrix;
     var rotatePoint:Point =
     rotateMatrix.transformPoint(
     new Point((displayO.width/2), (displayO.height/2)));
     rotateMatrix.translate(-rotatePoint.x, -rotatePoint.y);
     rotateMatrix.rotate(rotateEvent.rotation*(Math.PI/180));
     rotateMatrix.translate(rotatePoint.x, rotatePoint.y);
     displayO.transform.matrix = rotateMatrix ;
     }
    
     //
     //  Touch Event Handlers
     //
    
     private function onTouchBegin( event : TouchEvent ) : void
     {
     var p:Point;
     if(event.stageX == 0 && event.stageY == 0)
     {
     p = new Point(event.localX, event.localY);
     }
     else
     {
     p = new Point(event.stageX, event.stageY);
     }
     event.stopImmediatePropagation();
     pointMap[ event.touchPointID.toString() ] = p;
     addBlob( event.touchPointID, p.x, p.y );
     }
    
     private function onTouchEnd( event : TouchEvent ) : void
     {
     event.stopImmediatePropagation()
     removeBlob( event.touchPointID );
     delete pointMap[ event.touchPointID.toString() ];
     }
    
     private function onTouchMove( event : TouchEvent ) : void
     {
     event.stopImmediatePropagation()
     var p:Point;
     if(event.stageX == 0 && event.stageY == 0)
     {
     p = new Point(event.localX, event.localY);
     }
     else
     {
     p = new Point(event.stageX, event.stageY);
     }
    
     if ( pointMap[ event.touchPointID.toString() ] )
     {
     pointMap[ event.touchPointID.toString() ].x = p.x;
     pointMap[ event.touchPointID.toString() ].y = p.y;
     }
     update(null)
     }
    
     private function addBlob(id:Number, origX:Number, origY:Number):void
     {
     for(var i:int=0; i<blobs.length; i++)
     {
     if(blobs[i].id == id)
     return;
     }
    
     blobs.push( {id: id, origX: origX, origY: origY, myOrigX: displayO.x, myOrigY:displayO.y} );
    
     if(blobs.length == 1)
     {                
     state = DRAGGING;
     curScale = displayO.scaleX;
     curAngle = displayO.rotation;                    
     curPosition.x = displayO.x;
     curPosition.y = displayO.y;                
     blob1 = blobs[0];
     }
     else if(blobs.length == 2)
     {
     state = ROTATE_SCALE;
     curScale = displayO.scaleX;
     curAngle = displayO.rotation;                    
     curPosition.x = displayO.x;
     curPosition.y = displayO.y;        
    
     blob1 = blobs[0];                                
     blob2 = blobs[1];        
    
     var tuioobj1 : Object = pointMap[ blob1.id ];
     var tuioobj2 : Object = pointMap[ blob2.id ];
    
     var midPoint:Point = Point.interpolate(displayO.globalToLocal(new Point(tuioobj1.x, tuioobj1.y)),displayO.globalToLocal(new Point(tuioobj2.x, tuioobj2.y)),0.5);
    
     setRegistration(midPoint.x,midPoint.y);
    
     // if not found, then it must have died..
     if(tuioobj1)
     {
     var curPt1:Point = displayO.parent.globalToLocal(new Point(tuioobj1.x, tuioobj1.y));                                    
     blob1.origX = curPt1.x;
     blob1.origY = curPt1.y;
     }                
    
     }
    
     }
    
     private function removeBlob(id:Number):void
     {
     for(var i:int=0; i<blobs.length; i++)
     {
     if(blobs[i].id == id)
     {
     blobs.splice(i, 1);
    
     if(blobs.length == 0)
     state = NONE;
     if(blobs.length == 1)
     {
     state = DRAGGING;                    
    
     curScale = displayO.scaleX;
     curAngle = displayO.rotation;                    
     curPosition.x = displayO.x;
     curPosition.y = displayO.y;                    
    
     blob1 = blobs[0];        
    
     var tuioobj1 : Object = pointMap[ blob1.id ];
    
     if(tuioobj1)
     {                        
     var curPt1:Point = displayO.parent.globalToLocal(new Point(tuioobj1.x, tuioobj1.y));
    
     blob1.origX = curPt1.x;
     blob1.origY = curPt1.y;
     }
    
     }
     if(blobs.length >= 2) {
     state = ROTATE_SCALE;
    
     curScale = displayO.scaleX;
     curAngle = displayO.rotation;                    
     curPosition.x = displayO.x;
     curPosition.y = displayO.y;                
    
     blob1 = blobs[0];                                
     blob2 = blobs[1];        
    
     tuioobj1 = pointMap[ blob1.id ];
    
     if(tuioobj1)
     {
     curPt1 = displayO.parent.globalToLocal(new Point(tuioobj1.x, tuioobj1.y));                        
     blob1.origX = curPt1.x;
     blob1.origY = curPt1.y;
     }                                    
     }
     return;                    
     }
     }            
     }
    
     private function getAngleTrig(X:Number, Y:Number): Number
     {
     if (X == 0.0)
     {
     if(Y < 0.0)
     return 270;
     else
     return 90;
     } else if (Y == 0)
     {
     if(X < 0)
     return 180;
     else
     return 0;
     }
    
     if ( Y > 0.0)
     if (X > 0.0)
     return Math.atan(Y/X) * GRAD_PI;
     else
     return 180.0-Math.atan(Y/-X) * GRAD_PI;
     else
     if (X > 0.0)
     return 360.0-Math.atan(-Y/X) * GRAD_PI;
     else
     return 180.0+Math.atan(-Y/-X) * GRAD_PI;
     }         
    
     private function update(e:Event):void
     {
    
     if(state == DRAGGING)
     {
     var tuioobj:Object = pointMap[ blob1.id ];
    
     if(!tuioobj)
     {
     removeBlob(blob1.id);
     return;
     }
    
     var curPt:Point = displayO.parent.globalToLocal(new Point(tuioobj.x, tuioobj.y));  
    
     var oldX:Number, oldY:Number;
     oldX = displayO.x;
     oldY = displayO.y;
    
     displayO.x = (curPosition.x + (curPt.x - (blob1.origX )) > 0)? curPosition.x + (curPt.x - (blob1.origX ))  : 0 ;        
     displayO.y = (curPosition.y + (curPt.y - (blob1.origY )) > 0)? curPosition.y + (curPt.y - (blob1.origY ))  : 0;
    
     dX *= HALF;
     dY *= HALF;                        
     dAng *= HALF;
     dX += displayO.x - oldX;
     dY += displayO.y - oldY;        
    
     }
     else if(state == ROTATE_SCALE)
     {
    
     var tuioobj1 : Object = pointMap[ blob1.id ];
    
     if(!tuioobj1)
     {
     removeBlob(blob1.id);
     return;
     }                
    
     var curPt1:Point = displayO.parent.globalToLocal(new Point(tuioobj1.x, tuioobj1.y));                                
    
     var tuioobj2 : Object = pointMap[ blob2.id ];
     // if not found, then it must have died..
     if(!tuioobj2)
     {
     removeBlob(blob2.id);
     return;
     }                                
    
     var curPt2:Point = displayO.parent.globalToLocal(new Point(tuioobj2.x, tuioobj2.y));                
     var curCenter:Point = Point.interpolate(curPt1, curPt2, 0.5);    
    
     var origPt1:Point = new Point(blob1.origX, blob1.origY);
     var origPt2:Point = new Point(blob2.origX, blob2.origY);
     var centerOrig:Point = Point.interpolate(origPt1, origPt2, 0.5);
     var offs:Point = curCenter.subtract(centerOrig);
    
     var len1:Number = Point.distance(origPt1, origPt2);
     var len2:Number = Point.distance(curPt1, curPt2);                    
     var len3:Number = Point.distance(origPt1, new Point(0,0));
    
     var newscale:Number = curScale * len2 / len1;
    
     if(newscale<4)
     {
    
     setProperty2('scaleX', newscale);
     setProperty2('scaleY', newscale);
     }
     var origLine:Point = origPt1;
     origLine = origLine.subtract(origPt2);
    
     var ang1:Number = getAngleTrig(origLine.x, origLine.y);
    
     var curLine:Point = curPt1;
     curLine = curLine.subtract(curPt2);
    
     var ang2:int = getAngleTrig(curLine.x, curLine.y);
     var oldAngle:int = displayO.rotation;
    
     setProperty2("rotation", curAngle + (ang2 - ang1));    
    
     oldX = displayO.x;
     oldY = displayO.y;
    
     dX *= HALF;
     dY *= HALF;        
     dAng *= HALF;                
    
     dX += displayO.x - oldX;
     dY += displayO.y - oldY;
    
     dAng += displayO.rotation - oldAngle;
    
     } else {
     if(dX != 0 || dY != 0)
     {
     //this.released(dX, dY, dAng);
     dX = 0;
     dY = 0;
     dAng = 0;
     }
     }
    
     }    
    
     public function setRegistration(x:Number=0, y:Number=0):void{
     _rp = new Point(x, y);
     }
    
     public function setProperty2(prop:String, n:Number):void{
     var a:Point = new Point();
     var b:Point = new Point();
     if(displayO.parent != null){
     a = displayO.parent.globalToLocal(displayO.localToGlobal(_rp));
     displayO[prop] = n;
     b = displayO.parent.globalToLocal(displayO.localToGlobal(_rp));
     }else{
     a = displayO.localToGlobal(_rp);
     displayO[prop] = n;
     b = displayO.localToGlobal(_rp);
     }
     displayO.x -= b.x - a.x;
     displayO.y -= b.y - a.y;
     }
     }
    }

    8 Responses to “Rotatable Scalable (Part 2)”

    1. [...] Rotatable Scalable (Part 2) « MultiTouchup.com [...]

    2. Marco says:

      so cool~
      thanks your tutorials so much.

    3. Kene says:

      I’m not able to view your videos. Is that on my end or yours. Thanks for sharing your work.

    4. admin says:

      @Kene,

      Sorry about that. All of the video links broke yesterday. Should be fixed now. Thanks for pointing this out.

    5. Omer says:

      I wonder what the reason may be the position within the canvas has gone astray? English google translate :) sorry

    6. Lock says:

      You can add the source code of the project?

    7. admin says:

      I don’t have the original project but the key is this rotatable scalable class. With this class you can make any DisplayObject rotate and scale.

    8. Darren Ehlers says:

      Okay, I’m using your class in Flex Hero on Air for Android. It’s working good, but it’s limiting the zoom to only full-screen. I’d like to be able to zoom further into the image…basically allowing the display of an image that is larger then the physical screen.

      Thanks for any ideas/suggestions you can help with.

      Darren

    Your Reply