package { import flash.display.Sprite; import flash.display.Shape; import flash.events.Event; import flash.geom.*; import flash.display.StageAlign; import flash.display.StageScaleMode; import com.bit101.components.*; [SWF(backgroundColor="0xffffff", width="750", height="650", frameRate="30")] public class Forest extends Sprite { private var pathData:Vector. = new Vector.(); private var commands:Vector. = new Vector.(); private var dummy:Shape; private var len:Number = 750; private var iter:int = 6; private var rule:Vector.; private var canvas:Shape; public function Forest() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; init(); } private function init():void { canvas = new Shape(); canvas.x = 0; canvas.y = 500; canvas.rotation = 0; addChild(canvas); //create new displayObject to use localToGlobal() (must be in displayList for this to work) dummy = new Shape(); addChild(dummy); defineRules(); drawFractal(); addControls(); } private function defineRules():void { //there is only one: _/\__ (the angle is very acute on the sticking up part) rule = Vector.([ new Point(0,0), new Point(len/3,0), new Point(len/3 + len/50, -len*.47), new Point(len/3 + len/25,0), new Point(len,0) ]); } private function addControls():void { //depth control var iter_lbl:Label = new Label(this, 450, 15, "iterations:"); var iter_val:Label = new Label(this, 600, 15, ""+iter); var iter_sldr:HSlider = new HSlider(this, 500, 20, function(e:Event):void { iter = Math.ceil(e.target.value); iter_val.text = ""+iter; }); iter_sldr.setSliderParams(1,9,iter); //add redraw button var btn:PushButton = new PushButton(this, 620, 15, "redraw", function(e:Event):void { drawFractal(); }); } private function drawFractal():void { //clear data and commands pathData = new Vector.(); commands = new Vector.(); commands.push(1); //this one starts off with a straight line pathData.push(0,0,len,0);//length derived from rule above canvas.graphics.clear(); canvas.graphics.beginFill(0x00000); //we must interpolate new points between each two points in pathData //work on a copy of the pathData - keeps same length //whereas interpolated points are injected into pathData for(var i:int = 1; i <= iter; i++){ var pathDataCopy:Vector. = pathData.slice(0); var nmpts:Number = pathDataCopy.length; var ptIndex:Number = 0;//used to keep track of next point to interpolate //start on first point, take every pair after that for(var p:int = 0; p < nmpts-2; p+=2){ //ingterpolate between these two points var absVts:Vector. = getInterpolatedPoints( pathDataCopy[p],pathDataCopy[p+1],//first point pathDataCopy[p+2],pathDataCopy[p+3],//second point rule, i); //splice into pathData pathData.splice(ptIndex,4,absVts[0],absVts[1],absVts[2],absVts[3],absVts[4],absVts[5],absVts[6],absVts[7],absVts[8],absVts[9]); ptIndex += 8;//length of absVts - 2 } } //populate the commands vector with lineTo's for(var c:int = 1; c < pathData.length/2; c++){ commands.push(2); } //draw! canvas.graphics.drawPath(commands,pathData); //also draw 'ground' below canvas.graphics.drawRect(0,0,750,300); } private function getInterpolatedPoints(x1:Number,y1:Number,x2:Number,y2:Number,rule:Vector., it:int/*iteration*/):Vector. { //reset the dummy's transforms dummy.transform.matrix = new Matrix(); //position dummy at first point dummy.x = x1; dummy.y = y1; //to determine scale get distance between points by Pythagoras var d:Number = Math.sqrt((x2-x1)*(x2-x1) + (y2-y1)*(y2-y1)); //scale factor is then d/len dummy.scaleX = dummy.scaleY = d/len; dummy.rotation = 180*Math.atan2(y2-y1, x2-x1)/Math.PI; //for each point in the rule... //calculate absolute points var absVts:Vector. = new Vector.(8); //translate points from dummy shape to stage for(var pt:int = 0; pt < rule.length; pt++){ var apt:Point = dummy.localToGlobal(rule[pt]); absVts[pt*2] = apt.x; absVts[pt*2+1] = apt.y; } return absVts; } } }