Interactive Generative Art Series – 08 – multiple lines scalable


Well, so much for blogging each step in my process of creating this generative art series. I took a few steps in this iteration since the last one. But it was all in the spirit of one step. First I wanted to abstract the duplication of each ball/line so that it didn’t require copying blocks of code for each one. The obvious route is to have a var that designates the number of balls which will draw lines, and then loop through creating the desired number and then in the loop function that is executed each frame loop through them again and draw new lines for each one. This was pretty simple, in the end, and I ended up adding quite a few variables to differentiate each line for the others in line width, color variation and position. I also added a few function to help with calculations and updated the alpha values of the lines. The normalize, interpolate and map functions I acquired long ago from kp here, go there to hear his explanation which is much better than any attempt I could make. Thanks Keith!



Then, I had the task of making it more manageable and scalable. Before these edits, the framerate would sink to around 6 in the first 10 seconds of running with more than 3 lines. After some research it seemed the performance suffered mainly because the graphics drawn with transparency had to figure every single line on the stage to determine it’s color values and flash doesn’t do that too efficiently. So the solution was to use the bitmap and bitmapdata objects. Every frame I copy what has been drawn on the stage into a bitmapdata objects and set it to display on the stage instead. This essentially lets flash calculate the alpha values for each line once and then copy it as simple pixel data for later frames. It happens every frame and is in the flush function. It worked better than I had hoped. I could ramp up the number of lines to 40 and still not see any frame rate slowdown in FPS.

08 multiple lines scalable, play here

Please visit the blog article to view this interactive flash content. Flash plug-in required: Get Adobe Flash player

08 multiple lines scalable, another instance

Please visit the blog article to view this interactive flash content. Flash plug-in required: Get Adobe Flash player

actionscript source code

[cc lang=”actionscript”]
var balls:Array = new Array(); // array of ball objects – each ball object stores it’s own position (current & previous), acceleration, etc
var num_balls:int = 0;
var total_balls:int = 12;
var canvas:Sprite = new Sprite();
this.addChild(canvas);

//copy the graphics on stage to a bitmap, then next frame draw on it.
var bitmapcanvasdata:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, true, 0x000000);
//draw stage to bitmap
bitmapcanvasdata.draw(canvas);
var bitmapcanvas:Bitmap = new Bitmap(bitmapcanvasdata);
//put bitmap on stage
this.addChild(bitmapcanvas);

function createBall():void{
var ball_o:Object = new Object();

var ball:Sprite = new Sprite();
ball.graphics.beginFill(0x000000, .5);
ball.graphics.drawCircle(0, 0, 3);
ball.graphics.endFill();
addChild(ball);
ball_o.ball = ball;

ball_o.x = randomRangeAxis(10);
ball_o.y = 0;
ball_o.ax = 0;
ball_o.ay = 0;
ball_o.oldx = ball_o.ball.x;
ball_o.oldy = ball_o.ball.y;
ball_o.gradientBoxMatrix = new Matrix();
ball_o.drift = randomRangeAxis(10);
ball_o.color_drift = Math.floor(randomRangeAxis(4)) * 1024;
ball_o.line_width_drift = randomRangeAxis(2);
balls.push(ball_o);
}

for (var i:int = 0; i < total_balls; i++){ createBall(); } var anchor:Sprite = new Sprite(); anchor.graphics.beginFill(0x333333, .6); anchor.graphics.drawCircle(0, 0, 12); anchor.graphics.endFill(); addChild(anchor); var div:Number = .1; var line_max_width:Number = 64; var line_min_width:Number = 1; var line_width:Number = randomRange(line_min_width, line_max_width); var line_width_velocity:Number = 0; var dampen:Number = 0.95; var anchorvx:Number = 0; var anchorvy:Number = 0; anchor.x = stage.stageWidth/2; anchor.y = stage.stageHeight/2; var colors:Object = new Object(); colors.r = 150; colors.g = 100; colors.b = 200; colors.rv = 0; colors.gv = 0; colors.bv = 0; colors.rd = 100; colors.gd = 150; colors.bd = 200; colors.rmin = randomRange(0, 30); //0 colors.rmax = randomRange(colors.rmin, colors.rmin + colors.rd); //100 colors.gmin = randomRange(0, 60); //100 colors.gmax = randomRange(colors.gmin, colors.gmin + colors.gd); //200 colors.bmin = randomRange(0, 50); //150 colors.bmax = randomRange(colors.bmin, colors.bmin + colors.bd); //250 colors.rate_of_change = 10; var color_first:Number = 0xFFFFFF; var color_second:Number = rgb2hex(colors.r, colors.g, colors.b); function loop (e:Event = null) { //anchor anchorvx += randomRangeAxis(10); anchorvy += randomRangeAxis(10); anchor.x += anchorvx; anchor.y += anchorvy; anchorvx *= dampen; anchorvy *= dampen; if(anchor.x > stage.stageWidth) anchor.x = 0 – anchor.width;
else if(anchor.x < 0 - anchor.width) anchor.x = stage.stageWidth; if(anchor.y > stage.stageHeight) anchor.y = 0 – anchor.height;
else if(anchor.y < 0 - anchor.height) anchor.y = stage.stageHeight; //linewidth line_width_velocity += randomRangeAxis(1); line_width += line_width_velocity; line_width_velocity *= dampen; if(line_width > line_max_width) {
line_width = line_max_width;
line_width_velocity = 0;
}
else if (line_width < line_min_width) { line_width = line_min_width; line_width_velocity = 0; } //color step color_step(); color_first = color_second; color_second = rgb2hex(colors.r, colors.g, colors.b); //loop through balls and draw lines for (var i:int = 0; i < total_balls; i++){ balls[i].oldx = balls[i].ball.x; balls[i].oldy = balls[i].ball.y; balls[i].ball.x -= balls[i].ax = (balls[i].ax + (balls[i].ball.x - (anchor.x + randomRangeAxis(line_width * balls[i].drift))) * div) * .9; balls[i].ball.y -= balls[i].ay = (balls[i].ay + (balls[i].ball.y - (anchor.y + randomRangeAxis(line_width * balls[i].drift))) * div) * .9; balls[i].dx = balls[i].x - balls[i].oldx; balls[i].dy = balls[i].y - balls[i].oldy; canvas.graphics.moveTo(balls[i].oldx, balls[i].oldy); canvas.graphics.lineStyle(randomRangeAxis(balls[i].line_width_drift,line_width), color_first, 1, true, LineScaleMode.NONE, CapsStyle.NONE); balls[i].gradientBoxMatrix.createGradientBox(Math.abs(balls[i].dx), Math.abs(balls[i].dy), Math.atan2(balls[i].dy,balls[i].dx), Math.min(balls[i].oldx, balls[i].ball.x), Math.min(balls[i].oldy, balls[i].ball.y)); canvas.graphics.lineGradientStyle(GradientType.LINEAR, [color_first + balls[i].color_drift, color_second + balls[i].color_drift], [map(line_width,line_max_width,line_min_width, .1, .9),map(line_width,line_max_width,line_min_width, .1, .9)], [0, 255], balls[i].gradientBoxMatrix); canvas.graphics.lineTo(balls[i].ball.x, balls[i].ball.y); } //copy graphics drawings to bitmapdata and clear graphics flush(); } function flush():void { //draw stage to bitmap bitmapcanvasdata.draw(canvas); //replace bitmapdata of bitmap bitmapcanvas.bitmapData = bitmapcanvasdata; //erase vectors on stage canvas.graphics.clear(); } this.addEventListener(Event.ENTER_FRAME, loop) function rgb2hex(r:Number, g:Number, b:Number):Number { return(r<<16 | g<<8 | b); } function color_step(){ colors.rv += randomRangeAxis(colors.rate_of_change); colors.r += colors.rv; colors.rv *= dampen; if (colors.r > colors.rmax) {
colors.r = colors.rmax;
} else if (colors.r < colors.rmin){ colors.r = colors.rmin; } colors.gv += randomRangeAxis(colors.rate_of_change); colors.g += colors.gv; colors.gv *= dampen; if (colors.g > colors.gmax) {
colors.g = colors.gmax;
} else if (colors.g < colors.gmin){ colors.g = colors.gmin; } colors.bv += randomRangeAxis(colors.rate_of_change); colors.b += colors.bv; colors.bv *= dampen; if (colors.b > colors.bmax) {
colors.b = colors.bmax;
} else if (colors.b < colors.bmin){ colors.b = colors.bmin; } } //random number between min and max function randomRange(max:Number, min:Number = 0):Number { return Math.random() * (max - min) + min; } //random number range centered at 0 with the specified max, randomRange(-max, max) function randomRangeAxis(max:Number, axis:Number = 0):Number { return Math.random() * (max * 2) - max + axis; } //normalize(value, min, max) takes a value within a given range and converts it to a number between 0 and 1 (actually it can be outside that range if the original value is outside its range). function normalize(value:Number, minimum:Number, maximum:Number):Number { return (value - minimum) / (maximum - minimum); } //interpolate(min, max, value) is linear interpolation. It takes a normalized value and a range and returns the actual value for the interpolated value in that range. function interpolate(normValue:Number, minimum:Number, maximum:Number):Number { return minimum + (maximum - minimum) * normValue; } //map(value, min1, max1, min2, max2) takes a value in a given range (min1, max1) and finds the corresonding value in the next range(min2, max2). function map(value:Number, min1:Number, max1:Number, min2:Number, max2:Number):Number { return interpolate( normalize(value, min1, max1), min2, max2); } [/cc]

download

Here’s the gen-art-08-multiple-lines-scalable.swf as well as the gen-art-08-multiple-lines-scalable.fla to download and explore. And as always if you’ve got ideas or suggestions, comment below. One thing I’m struggling with is that now although the experiment is truly generative, it’s no longer interactive.