In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

Blob simulation with Verlet integration

[arrow key or mouse drag] move the blob
[d] toggle debug output
todo
* modify the blob not to slip 
* modify the blob to rotate more naturally
and more...
Get Adobe Flash player
by jerryrom 01 Apr 2010
/**
 * Copyright jerryrom ( http://wonderfl.net/user/jerryrom )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/rwRa
 */

// [arrow key or mouse drag] move the blob
// [d] toggle debug output

// todo
// * modify the blob not to slip 
// * modify the blob to rotate more naturally
// and more...

package {
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.events.KeyboardEvent;
	import flash.geom.Point;
	import flash.geom.Rectangle;
	import flash.ui.Keyboard;
	import net.hires.debug.Stats;
	/**
	 * ...
	 * @author Masahiro Hayashi
	 */
	[SWF(width=465, height=465, backgroundColor=0xA9D0F5, frameRate=50)]
	public class Main extends Sprite {
		private const N:uint = 50;
		private var points:Vector.<VerletPoint>;
		private var sticks:Vector.<VerletStick>;
		private var cw:Number;
		private var ch:Number;
		private var stageRect:Rectangle;
		private var gravity:Number;
		private var guide:Sprite;
		private var stats:Stats;
		private var debug:Boolean = false;
		
		public function Main():void {
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
		
		private function init(e:Event = null):void {
			addChild(new Stats());
			
			removeEventListener(Event.ADDED_TO_STAGE, init);
			// entry point
			graphics.beginFill(0xA9D0F5);
			graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
			graphics.endFill();
			
			guide = new Sprite();
			addChild(guide);
			
			stageRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight);
			cw = stage.stageWidth / 2;
			ch = stage.stageHeight / 2;
			points = new Vector.<VerletPoint>();
			sticks = new Vector.<VerletStick>();
			
			gravity = 0.1;

			points.push(new VerletPoint(cw, ch));
			var i:uint = 0;
			var r:Number = 2 * Math.PI / N;
			for (i = 1; i <= N; i++) {
				points.push(new VerletPoint(cw + Math.cos(r * i) * 50, ch + Math.sin(r * i) * 50));
				sticks.push(new VerletStick(points[0], points[i], 0.2));
			}
			for (i = 1; i <= N; i++) {
				if (i == N) {
					sticks.push(new VerletStick(points[i], points[1], 0.02))
				} else {
					sticks.push(new VerletStick(points[i], points[i + 1], 0.02));
				}
			}
			/*
			for (i = 1; i <= N; i++) {
				if (i == N - 1) {
					sticks.push(new VerletStick(points[i], points[1]))
				} else if (i == N) {
					sticks.push(new VerletStick(points[i], points[2]))
				} else {
					sticks.push(new VerletStick(points[i], points[i + 2]));
				}
			}*/
			stage.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
			stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
			stage.addEventListener(KeyboardEvent.KEY_DOWN, onKeyDown);
			this.addEventListener(Event.ENTER_FRAME, onFrame);
		}
		
		private function onMouseUp(e:MouseEvent):void { 
			guide.graphics.clear();
		}
		
		private function onMouseDown(e:MouseEvent):void {
			if (debug) {
				guide.graphics.clear();
				guide.graphics.lineStyle(1, 0xff0000, 1);
				guide.graphics.moveTo(points[0].x, points[0].y);
				guide.graphics.lineTo(mouseX, mouseY);
			}
		}
		
		private function onMouseMove(e:MouseEvent):void {
			if (e.buttonDown) {
				points[0].x += (mouseX - points[0].x) * 0.1;
				points[0].y += (mouseY - points[0].y) * 0.1;
				if (debug) {
					guide.graphics.clear();
					guide.graphics.lineStyle(1, 0xff0000, 1);
					guide.graphics.moveTo(points[0].x, points[0].y);
					guide.graphics.lineTo(mouseX, mouseY);
				}
			} else {
				guide.graphics.clear();
			}
		}
				
		private function onKeyDown(e:KeyboardEvent):void { 
			var n:Number = N;
			while(n--) {
				switch (e.keyCode) {
					case Keyboard.LEFT:  points[n].x -= 0.3; break;
					case Keyboard.RIGHT: points[n].x += 0.3; break;
					case Keyboard.UP:    points[n].y -= 0.7; break;
					case Keyboard.DOWN:	 points[n].y += 0.3; break;
					default: break;
				}
			}
			if (e.keyCode == 68) debug = !debug;
		}
		
		private function onFrame(e:Event):void {
			var i:uint;
			for (i = 0; i < N + 1; i++) {
				points[i].y += gravity;
				points[i].update();
				points[i].constrain(stageRect);
			}
			for (i = 0; i < N * 2; i++) {
				sticks[i].update();
			}
			for (i = 0; i < N * 2; i++) {
				sticks[i].update();
			}/*
			for (i = 0; i < N * 2; i++) {
				sticks[i].update();
			}*/

			//render
			graphics.clear();
			if (debug) { graphics.lineStyle(1, 0x000000, 1); graphics.beginFill(0xffffff); }
			else { graphics.beginFill(0xffffff);}

			for (i = 0; i < N; i++) {
				graphics.moveTo(sticks[i].pointA.x, sticks[i].pointA.y);
				graphics.lineTo(sticks[i + N].pointB.x, sticks[i + N].pointB.y);
				if (i == N-1) {
					graphics.lineTo(sticks[1].pointB.x, sticks[1].pointB.y);
				} else {
					graphics.lineTo(sticks[i+N+1].pointB.x, sticks[i+N+1].pointB.y);
				}
				graphics.lineTo(points[0].x, points[0].y);
			}

			if (debug) graphics.lineStyle(1, 0xff0000, 1);
			graphics.moveTo(points[0].x, points[0].y);
			graphics.lineTo(points[1].x, points[1].y);
			graphics.endFill();
		}
		
	}
	
}


	internal class VerletStick{
		public var pointA:VerletPoint;
		public var pointB:VerletPoint;
		private var length:Number = -1;
		private var min:Number;
		private var max:Number;
		private var softness:Number;
		
		public function VerletStick(pointA:VerletPoint, pointB:VerletPoint, softness:Number = -1) {
			this.pointA = pointA;
			this.pointB = pointB;
			this.softness = softness;

			var dx:Number = pointA.x - pointB.x;
			var dy:Number = pointA.y - pointB.y;
			length = Math.sqrt(dx * dx + dy * dy);
			
		}
		
		public function update():void {
			var dx:Number = 0;
			var dy:Number = 0;
			var dest:Number = 0;
			var diff:Number = 0;
			var offsetX:Number = 0;
			var offsetY:Number = 0;
					
			var n:int = 10;
			while (n--) {
			
				dx = pointB.x - pointA.x;
				dy = pointB.y - pointA.y;
				dest = Math.sqrt(dx * dx + dy * dy);
				diff = length - dest;
				
				if (softness != -1) {
					
					min = length * (1 - softness);
					max = length * (1 + softness);

					if (dest < min) {
						diff = min - dest;
					} else if (max < dest) {
						diff = max - dest;
					} else {
						diff = (length - dest) * 0.05;
					}

					offsetX = (diff * dx / dest) / 16;
					offsetY = (diff * dy / dest) / 16;
					
				} else {
					dx = pointB.x - pointA.x;
					dy = pointB.y - pointA.y;
					dest = Math.sqrt(dx * dx + dy * dy);
					
					diff = length - dest;
					
					offsetX = (diff * dx / dest) / 2;
					offsetY = (diff * dy / dest) / 2;
				}
				
				//if (pointA.y - offy >= 465 || pointB.y + offy >= 465) {
					//offx *= 0.1;
				//}
				pointA.x -= offsetX;
				pointA.y -= offsetY;
				pointB.x += offsetX;
				pointB.y += offsetY;
			}
		}
		
	}

		import flash.geom.Rectangle;
	/**
	 * ...
	 * @author Masahiro Hayashi
	 */
	internal class VerletPoint{
		public var x:Number;
		public var y:Number;
		private var oldx:Number;
		private var oldy:Number;
		
		public function VerletPoint(x:Number, y:Number) {
			setPos(x,y);
		}
		
		public function setPos(x:Number, y:Number):void{
			this.x = oldx = x;
			this.y = oldy = y;
		}
		
		public function update():void {
			var tempx:Number = x;
			var tempy:Number = y;
			x += x - oldx;
			y += y - oldy;
			oldx = tempx;
			oldy = tempy;
		}
		
		public function constrain(rect:Rectangle):void {
			x = Math.max(rect.left, Math.min(rect.right, x));
			y = Math.max(rect.top, Math.min(rect.bottom, y));
		}
		
		public function copy ():VerletPoint {
			return new VerletPoint(x, y);
		}
	}