Understanding RotatableScalable.as

From NUI Group Community Wiki

Jump to: navigation, search

The is something powerful at your disposal in the touchlib library for AS3. It is called RotatableScalable.as

Now, it is a bit messy. And there is code in there that doesn't really need to be, but it is a Great place to start if all you want is to Drag, Scale and Rotate MovieClips. Unfortunetly, it's not without bugs to work through. Lets do a really quick test in Flash.

You may know the drill by now.

  1. Make yourself a new folder to contain this project.
  2. Add in the "flash\events\TUIO" AS section for TUIO communications
  3. Make a new FLA, lets call this one "firstTest.fla"
  4. Size 1024x768
  5. Add a square to cover the stage
  6. Make a new AS file in a new folder "app\FirstTest.as"
  7. Back in firstTest.fla in the properties dialog, Document Class enter: app.FirstTest (this hooks up the FLA and AS file)
  8. And with in the touchlib library find this path: \AS3\int\app\core\action
    1. In there, you'll find a file called RotatableScalable.as
    2. you need to add that file to this folder structure: Your Custom Folder\app\core\action

Hopefully you have all your files ready, if not, there will be a link to a zip file containing there files for you to kind of work along with. And for reference, I'm using http://wiki.nuigroup.com/Building_Your_First_Multi-Touch_Application_in_Flash. Look about 3/4 down the page. There is demo code that creates rings on the stage you can scale and drag.

Now back to FirstTest.as

package app{

	import flash.display.Sprite;
	import flash.events.TUIO;

	public class FirstTest extends Sprite {

		var test:TestItem;

		public function FirstTest():void {
			//--------connect to TUIO-----------------
			TUIO.init(this,'localhost',3000,'',true);
			trace("TUIO Initialized");
			//----------------------------------------

			test = new TestItem();
			addChild(test);
			test.x = 270;
			test.y = 370;

		}
	}
}
import flash.display.Sprite;// ring class
import app.core.action.RotatableScalable;

class TestItem extends RotatableScalable {

	public function TestItem() {

		//---RotatableScalable options------------
		//noScale = true;
		//noRotate = true;
		//noMove = true;
		//----------------------------------------
		graphics.beginFill(0xffffff);
		graphics.drawRect(0,0,150,150);
		graphics.endFill();
	}
}

So here's a small app to play around with. You should see things you don't expect to happen, happen. First of, run it. Did you see the compiler errors?

  • RotatableScalable.as Line 179 Warning: 3596: Duplicate variable definition.
  • RotatableScalable.as Line 518 Warning: 3596: Duplicate variable definition.

So what's the problem? Its nothing huge, its not like the app won't function at all because of these errors. But I like my compiler to be clean of errors. Open the RotatableScalable.as file and lets look at the first line error.

var curPt1:Point = parent.globalToLocal(new Point(tuioobj1.x, tuioobj1.y));

Ok, it's getting the X and Y value of a touchpoint. Nothing wrong with that, its a good thing. but at this point, the variable already exists, so it doesn't need to be created again. We don't want to get rid of the variable because it is figuring out a value to be used, we just want to adjust how its written.

curPt1 = parent.globalToLocal(new Point(tuioobj1.x, tuioobj1.y));

Piece of cake, how about the other one?

var oldX:Number, oldY:Number;

Well, this line is just redeclairing variables that already exist. This one we don't need, so you can comment out this line, or delete it.

Now retest the Flash movie. We have a clean compile. Excellent! Lets try some dragging......ok, works great, no problems there. How about scaling and rotating (which kind of end up going hand in hand).

What's this?! What is this. Something ain't right here. What's going on? If we use the demo code from the wiki, this doesn't happen. Ok, here's the thing. When the ring is created, the 0,0 coordinates of the circle is the dead center of it. But when you create something like a rectangle, the 0,0 is in the top right. And this current version of RotatableScalable.as seems to scale around 0,0 coordinate of the object rather than calculating the point inbetween the two touchpoints.

I am in no way claiming that I found this bug, or am going to fix it here. It was actually fixed all the way back in July 08, by weyforth in the NUIGroup Forums. So much acclaim is given to weyfort. I just haven't seen it added into the touchlib code. If you want to read more about the events leading to the bug fix http://nuigroup.com/forums/viewthread/2125/P45/. In the thread weyforth added a file to http://nuigroup.com/?ACT=28&fid=33&aid=1308_TxiZt1sbvToGbtZiJPin <-- download with a bug fixed RotatableScalable.as

Go head and download the file to use.

Next up I want to add the file to this folder Your Custom Folder\app\core\action, where the original file is. But lets not over write it right away. Time for some constructive renaming. Save the revised file as RotatableScalableRev.as and open it. We need to do a quick change to help connect the file to an FLA.

On line 12:

public dynamic class RotatableScalableRev extends MovieClip

then on line 49:

        public function RotatableScalableRev()

The filename, the class and the initial function all have to be the same name for Flash to be able to connect the files.

Time for a new FLA file. Let's call this one "newTest.fla" and a new AS in the app folder "app\NewTest.as"

In the Document Class in the FLA, change it to: app.NewTest

And now some new code for NewTest.as

package app{

	import flash.display.Sprite;
	import flash.events.TUIO;

	public class NewTest extends Sprite {

		var newMTItem:TestItemNewVer;

		public function NewTest():void {
			//--------connect to TUIO-----------------
			TUIO.init(this,'localhost',3000,'',true);
			trace("TUIO Initialized");
			//----------------------------------------

			newMTItem = new TestItemNewVer();
			addChild(newMTItem);
			newMTItem.x = 720;
			newMTItem.y = 370;

		}
	}
}

import flash.display.Sprite;// ring class
import app.core.action.RotatableScalableRev;

class TestItemNewVer extends RotatableScalableRev {

	public function TestItemNewVer() {

		//---RotatableScalable options------------
		//noScale = true;
		//noRotate = true;
		//noMove = true;
		//----------------------------------------
		graphics.beginFill(0xffffff);
		graphics.drawRect(0,0,150,150);
		graphics.endFill();
	}
}

Now if all goes right, run yourself a test movie and see what happens. ~ ut oh. compiler errors. But not to worry, they are the same errors from before, we know how to fix those now.

So now what happens when you scale and rotate the rectangle? Phew, that looks like it work a LOT better. But why? I'm tracing through that right now. My first reaction is magic, but that is not much of an explination. So I'll try best I can.

It start with using an array to track how many blobs are 'touching' the object, public var blobs:Array;

On a touch down, it grabs the information of the object, X Y rotation scale, and uses that as a baseline for adjustments. It also marks it for an ENTER_FRAME event to know when and how to change the object. Either "dragging" or "rotatescale"

Line 426: private function update(e:Event):void is where the bulk of the operation happens.

running through, first it checks to see if it is just dragging the object.

				if(!noMove)
				{
					x = curPosition.x + (curPt.x - (blob1.origX ));
					y = curPosition.y + (curPt.y - (blob1.origY ));
				}

this one easy. It moves the object according to how much your finger have moved on both X and Y coordinates.

The other one, I'm really not sure how to explain. And I think this may be my first big "I've just written myself into a corner" event. From what I can tell, there is only a different between the two files in how the properties are set for scale and rotation.

Scale:

setProperty2('scaleX', newscale);
setProperty2('scaleY', newscale);

androtation:

setProperty2("rotation", curAngle + (ang2 - ang1));

and dragging:

x2 = curCenter.x;
y2 = curCenter.y;

So what is setProperty2? And how does it adjust the registration point? I'm really not sure, but I am 89% sure it has to do with this code.

                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(this.parent != null){
				a = this.parent.globalToLocal(this.localToGlobal(_rp));
				this[prop] = n;
				b = this.parent.globalToLocal(this.localToGlobal(_rp));
			}else{
				a = this.localToGlobal(_rp);
				this[prop] = n;
				b = this.localToGlobal(_rp);
			}
			this.x -= b.x - a.x;
			this.y -= b.y - a.y;
		}

		public function get x2():Number{
			var p:Point = this.parent.globalToLocal(this.localToGlobal(_rp));
			return p.x;
		}

		public function set x2(value:Number):void{
			var p:Point = this.parent.globalToLocal(this.localToGlobal(_rp));
			this.x += value - p.x;
		}

		public function get y2():Number{
			var p:Point = this.parent.globalToLocal(this.localToGlobal(_rp));
			return p.y;
		}

		public function set y2(value:Number):void{
			var p:Point = this.parent.globalToLocal(this.localToGlobal(_rp));
			this.y += value - p.y;
		}

globalToLocal, localToGlobal, getters, setters, setRegistration. What? Ok, looks like I have more to learn. But one thing I will take away from all this, the revised version works the way you think it would. Oh and one more thing, weyforth is brilliant!

So in the end. I learned a little, hopefully passed on at least something useful for you the reader. I zipped up some files, there is an extra example with a 2 grids, one using the old file, and one with the new. http://multitouchas3experiments.googlecode.com/files/RotatableScalable_test.zip.

Happy Experimenting.