Flex/Flash Actionscript, JQuery/Javascript, CakePHP

Flex 4 elastic rope mxml component

Written by Brandon Plasters on 8/11/09 with 6 Comments
Posted in Flex / Flash / Actionscript

So there I was, in need of attaching a hamster to a balloon with an elastic cord; I had the balloon and the hamster, but was missing the critical elastic. I googled for a half-hour and stumbled across a great Actionscript class by Maxim Sprey (thank you Maxim!).

I converted Maxim's AS3 class into a Flex mxml rope component where you can attach two objects to each end, an anchored object and a swinging object (which would be the balloon and the hamster respectively). You instantiate the component in mxml like this:

<components:Rope id="rope" swingObject="swingButton" anchorObject="anchorButton"/>

Pretty self expanitory- swingObject is the object attached to the end of the rope that swings, and the anchor object anchors the rope. I've substituted buttons for the hamster and balloon in this example for simplicity, but you can see the hamster and balloon version at tjmillerdoesnothaveawebsite.com, one of my latest sites at the time of this posting.

Drag and release the small button below in the example to see the full elastic effect.

To view this page ensure that Adobe Flash Player version
10.0.0 or greater is installed.


The ElasticRope component uses the very kick ass TweenMax tweening framework from GreenSock. I recommend downloading the entire Tween* library as it is the best for AS3 tweening and one of the most ubiquitous libraries in my apps. But if you'd rather not, I only use the TweenMax.delayedCall method in this component, so altering the code below to use a Timer instead would not be difficult.

The code for the above example ElasticRopeTest.mxml application is below, and below that is the ElasticRope.mxml component code:



	
		<![CDATA[
			import mx.core.FlexGlobals;

			private var mouseDown:Boolean = false;

			public function init():void {
				addEventListener(MouseEvent.MOUSE_UP, endDrag)
			}

			public function ropeFrameHandler(e:Event):void {
				if(mouseDown == true){
					rope.swingPoint = new Point(mouseX,mouseY);
				}
			}

			private function swingDragHandler(me:MouseEvent):void{
				mouseDown=true;
			}

			private function endDrag(me:MouseEvent):void{
				mouseDown=false;
			}

		]]>
	
	
		
	

	
		
	
	

Full Code for ElasticRope.mxml:



	
		
	

	
		<![CDATA[
		import gs.TweenMax;
		import mx.core.FlexGlobals;
		import mx.events.StateChangeEvent;
		import spark.components.Group;
		// Stick properties
		private var mass:Number = 2;
		private var len:Number = .5;
		private var div:Number = 7;
		private var massDiv:Number = mass / div;
		private var lenDiv:Number = len / div;
		// Circle properties
		private var cX:Number = 300;
		private var cY:Number = 200;
		private var cR:Number = 50;
		// World Characteristics
		private var g:Number  = 9.81;
		private var pm:Number = 100; // Pixels/meter
		private var dt:Number = 1 / 60;
		private var itr:uint = 3; // Number of rigid body iterations
		// Program Characteristics
		private var pX:Array = [];
		private var pY:Array = [];
		private var oX:Array = [];
		private var oY:Array = [];
		private var aX:Array = [];
		private var aY:Array = [];
		private var mS:Group = new Group;
		private var drawing:Boolean=false;
		private var chainLinkRadius:Number = 3;
		private var _swingPoint:Point = new Point(0,0)

		public var chainColor:Number = 0x666666
		public var anchorPoint:Point = new Point(0,0)
		public var swingPointRotation:Number = 0;
		public var anchorPointRotation:Number = 0;
		public var anchorObject:*
		public var swingObject:*
		public var enterFrameCallBack:Function;

		public function set swingPoint(value:Point):void{
			_swingPoint = value;
			pX[div] = _swingPoint.x
			pY[div] = _swingPoint.y
		}

		public function get swingPoint():Point{
			return _swingPoint;
		}

		private function init():void{
			for (var i:uint = 0; i <= div; i++) {
				pX[i] = 10 + (lenDiv * pm * i);
				pY[i] = 10;
				oX[i] = 10 + (lenDiv * pm * i);
				oY[i] = 10;
				aX[i] = 0;
				aY[i] = 0;
			}
			addElement(mS);
			addEventListener(Event.ENTER_FRAME, frame);
		}

		private function enforceDrawing(e:StateChangeEvent):void{
			//if you left the home state and drawing was set to true, it would never
			//resume drawing = false, therfore hammy's rope would not work anymore
			if(e.newState == 'home'){
				drawing = false
			}
		}

		private function frame(e:Event):void{
			if(drawing == false){
				draw();
			}
		}

		private function draw():void{
 			drawing=true
 			//pX[0] = stage.mouseX;
			//pY[0] = stage.mouseY;
			anchorPoint.x = pX[0]
			anchorPoint.y = pY[0]
			swingPoint = new Point(pX[div],pY[div]);
			if(anchorObject){
				pX[0] = anchorObject.x;
				pY[0] = anchorObject.y;
			}
			if(swingObject){
				swingObject.x=swingPoint.x
				swingObject.y=swingPoint.y
			}
			accForces();
			verlet();
			//checkColl();
			for (var j:uint = 0; j <= div;j++){
				satConstraints();
			}
			// Draw line
			mS.graphics.clear();
			mS.graphics.lineStyle(1, chainColor, 1);
			mS.graphics.moveTo(0, 0);
			mS.graphics.moveTo(pX[0], pY[0]);
			for (var i:uint = 0; i <= div; i++) {
				mS.graphics.moveTo(pX[i], pY[i]);
				mS.graphics.drawCircle(pX[i], pY[i], chainLinkRadius);
			}

			swingPointRotation = rotationFromAngle(new Point(pX[div],pY[div]), new Point(pX[div-1],pY[div-1]));
			anchorPointRotation = rotationFromAngle(new Point(pX[0],pY[0]), new Point(pX[1],pY[1]));

			TweenMax.delayedCall(.01,function():void{drawing = false})

		}

		public function verlet():void {
			for (var i:uint = 0; i <= div; i++) {
				var tempX:Number = pX[i];
				pX[i] += (0.99*pX[i] - 0.99*oX[i]) + (aX[i] * pm * dt * dt);
				var tempY:Number = pY[i];
				pY[i] += (0.99*pY[i] - 0.99*oY[i]) + (aY[i] * pm * dt * dt);
				oX[i] = tempX;
				oY[i] = tempY;
			}
		}
		public function accForces():void {

			for (var i:uint = 1; i <= div; i++) {
				aY[i] = g;
			}
		}

		public function satConstraints():void {
			for (var i:uint = 1; i <= div; i++) {
				var dx:Number = (pX[i] - pX[i - 1]) / pm;
				var dy:Number = (pY[i] - pY[i - 1]) / pm;
				var d:Number = Math.sqrt((dx * dx) + (dy * dy));
				var diff:Number = d - lenDiv;
				pX[i] -= (dx / d) * 0.5 *pm* diff;
				pY[i] -= (dy / d) * 0.5 *pm* diff;
				pX[i - 1] += (dx / d) * 0.5 *pm* diff;
				pY[i - 1] += (dy / d) * 0.5 *pm* diff;
			}
		}

		public function checkColl():void {
			for (var i:uint = 0; i <= div; i++) {
				var dx:Number = pX[i] - cX;
				var dy:Number = pY[i] - cY;
				var r:Number = Math.sqrt((dx * dx) + (dy * dy));
				var rr:Number = 	r-cR;
				if (rr < 0) {
					// Collision
					pX[i] += ( -dx / r) * rr;
					pY[i] += ( -dy / r) * rr;
				}
			}
		}

		public static function rotationFromAngle(first:Point, second:Point):Number {

			var dY:Number = first.y - second.y
			var dX:Number = first.x - second.x
			var angle:Number = Math.atan((dY) / (dX)) / (Math.PI / 180)
			if (dX < 0) {
				angle +=180;
			}
			return angle
		}

		protected function group1_enterFrameHandler(event:Event):void
		{

			dispatchEvent(new Event('ropeEnterFrame'))
		}
		]]>
	

Please let me know of any imporvements you think I should add. Also let me know if I forgot to credit someone for any code snippits. Enjoy!

Comments

Posted on 10/11/09

Well done. Love the farm & the lil racoon farmer! The code rendering is tight too, as well as the content. Well done!

#2 jeff
Posted on 15/11/09

One question, how would I add the rotation angle to the button in the above example similar to how the hamster rotates with the rope in your demonstration site, tjmillerdoesnothaveawebsite.com?

#3 Brandon
Posted on 15/11/09

@jeff: to rotate the swingObject, just add this line to the example application code above at the end of the ropeFrameHandler function:

swingButton.rotation = rope.swingPointRotation - 90
Posted on 18/11/09

Hi, saw you poppin up in google alerts, the article is over here, http://www.functionblog.com/?p=134. Looks good! I'd advise you to fit the code in classes though (maybe something like particle and constraint classes?) anyway good luck with the rest and happy coding...

#5 Brandon
Posted on 18/11/09

@Maxim: Next time I have to revisit this I'll use your advice. Since this component has no mxml, a class make more sense. Even further, multiple classes as you suggested. Thanks again.

Posted on 25/1/10

Nice post,
Great information about mxml components,
Keep up the good work

Leave a Comment

Please be respectful; your comment my be edited or marked as spam, if necessary.