I know, I know. Recursive trees are a thing that have been done to death by – well – pretty much everyone.
I just picked up a nice book on Processing the other day though and one of the first examples it gives is a nice recursive tree. In order to better understand the java/processing involved, I thought I’d take the time to convert it to a language I know. Actionscript, that is (in case you were wondering).
After I got it ported and figured out what it was doing and why, I started thinking, that might look kinda cool in 3D. Then I remembered Seb Lee-Delisle had created a ridiculously simple to use 3d drawing api, so I put the two together and came up with the below (roll over to spin the tree around and click to generate a new one).
[kml_flashembed publishmethod=”static” fversion=”10.0.0″ movie=”https://blog.onebyonedesign.com/wp-content/uploads/2010/03/tree.swf” width=”400″ height=”600″ targetclass=”flashmovie”]
[/kml_flashembed]
If you’d like to have a play with it yourself, the code’s below (and of course you’ll need Seb’s stuff from the link above).
Main
package {
import com.sebleedelisle.draw3d.Graphics3D;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import geom.Point3D;
/**
* 3D recursive tree based on processing example from
* "Processing: Creative Coding and Computational Art"
* http://www.amazon.com/Processing-Creative-Coding-Computational-Foundation/dp/159059617X/
*
* Using Seb Lee-Delisle Graphics3D lib
* http://sebleedelisle.com/2009/11/simple-flash-3d-drawing-api/
*
*
* @author Devon O.
*/
[SWF(width='400', height='600', backgroundColor='#C0C0C0', frameRate='31')]
public class Main extends Sprite {
public static const DARK_BROWN:uint = 0x5C3317;
private var counter:int = 0;
private var counter2:int = 0;
private var xg:Number = 5;
private var yg:Number = 40;
private var zg:Number = 5;
private var trunkSegments:int = int(Math.random() * 4 + 3);
private var pts:Vector. = new Vector.();
private var branchLimit:int = 325;
private var trunkLength:int = int(Math.random() * 50 + 130);
private var lean2:Vector. = new Vector.(trunkSegments + 1, true);
private var radius:Number = 8;
private var _g3d:Graphics3D;
public function Main():void {
if (stage) init();
else addEventListener(Event.ADDED_TO_STAGE, init);
}
private function init(e:Event = null):void {
removeEventListener(Event.ADDED_TO_STAGE, init);
// just some quick text
var tf:TextField = new TextField();
tf.selectable = false;
tf.mouseEnabled = false;
tf.defaultTextFormat = new TextFormat("_sans", 11);
tf.autoSize = TextFieldAutoSize.LEFT;
tf.text = "Click to generate a new tree.";
tf.x = int(stage.stageWidth * .5 + 20);
tf.y = int(stage.stageHeight - tf.height);
addChild(tf);
_g3d = new Graphics3D(this);
trunk();
addEventListener(Event.ENTER_FRAME, rotate);
stage.addEventListener(MouseEvent.CLICK, drawTree);
}
// set all vars back to original
private function reset():void {
counter = 0;
counter2 = 0;
xg = 5;
yg = 40;
zg = 5;
trunkSegments = int(Math.random() * 4 + 3);
pts = new Vector.();
trunkLength = int(Math.random() * 50 + 130);
lean2 = new Vector.(trunkSegments + 1);
radius = 5;
}
private function drawTree(event:MouseEvent):void {
_g3d.clear();
reset();
trunk();
}
private function rotate(event:Event):void {
var ratio:Number = ((stage.mouseX / stage.stageWidth) - .5) * 2;
_g3d.rotateY(ratio * 4);
}
// draws the tree
private function trunk():void {
for (var i:int = 0; i < trunkSegments; i++) {
var lean:Number = randRange(22);
_g3d.lineStyle(radius + 3, DARK_BROWN);
_g3d.moveTo2D(stage.stageWidth / 2 + lean2[i], stage.stageHeight - (trunkLength / trunkSegments) * i, 0);
_g3d.lineTo2D(stage.stageWidth / 2 + lean, stage.stageHeight - (trunkLength / trunkSegments) * (i + 1), 0);
lean2[i + 1] = lean;
}
// set inital branch point from top of trunk
pts[0] = new Point3D(stage.stageWidth * .5 + lean2[trunkSegments], stage.stageHeight - trunkLength, 0);
//create branches
branch(pts);
}
private function branch(pts:Vector.):void {
var stemCount:int = 2;
// branchLimit controls complexity of tree
if (counter2 < branchLimit){
//set branch thickness
_g3d.lineStyle(radius, DARK_BROWN);
// some conditionals change branches as
// they get further away from the trunk
if(counter2 < 200) {
yg -= Math.random() * .354;
xg -= Math.random() * .625;
if (radius > 2) radius *= .85;
} else if (counter2 >= 200) {
// moving into leaf territory now
// at top of tree branches get thinner and more numerous
stemCount = 2 + int(Math.random() * 5);
// leaf color
var leafColor:uint = getColor(Math.random() * 60, 50 + Math.random() * 90, Math.random() * 20);
_g3d.lineStyle(0, leafColor, 230 / 255);
yg -= Math.random() * .75;
xg *= Math.random() * .20;
zg *= Math.random() * .20;
}
for (var j:int = 0; j < stemCount; j++) {
// randomize branch positions
var xx:Number = randRange(30);
var yy:Number = randRange(40);
var zz:Number = randRange(50);
_g3d.moveTo2D(pts[counter2].x, pts[counter2].y, pts[counter2].z);
_g3d.lineTo2D(pts[counter2].x + xg + xx, pts[counter2].y - yg + yy, pts[counter2].z + zg + zz);
// fill up pts array to be passed back recursively to branch function
pts[counter + 1] = new Point3D(pts[counter2].x + xg + xx, pts[counter2].y - yg + yy, pts[counter2].z + zg +zz);
// alternate branches left and right and back and forth
xg *= -1;
zg *= -1;
// keep track of nodes
counter++;
}
// keeps track of branches
counter2++;
//recursive call
branch(pts);
}
}
private function getColor(r:Number, g:Number, b:Number):uint {
return r << 16 | g << 8 | b;
}
private function randRange(val:int):Number {
return Math.random() * val + Math.random() * -val;
}
}
}
and a simple Point3D class to just store a bit of data
package geom {
/**
* Basic 3D point
* @author Devon O.
*/
public class Point3D {
private var _x:Number;
private var _y:Number;
private var _z:Number;
public function Point3D(x:Number = 0, y:Number = 0, z:Number = 0) {
_x = x;
_y = y;
_z = z;
}
public function get x():Number { return _x; }
public function set x(value:Number):void {
_x = value;
}
public function get y():Number { return _y; }
public function set y(value:Number):void {
_y = value;
}
public function get z():Number { return _z; }
public function set z(value:Number):void {
_z = value;
}
}
}
A Saturday morning well spent...
In other news, just discovered this blog's been shortlisted as Best Irish Tech Blog. Some very nice news!