Latest snippets

Catmull-Rom Spline

var canvas:BitmapData = new BitmapData(400,400,false, 0x000000);
addChild(new Bitmap(canvas));

var p0:Sprite = dot(100, 100);
var p1:Sprite = dot(120, 180);
var p2:Sprite = dot(220, 180);
var p3:Sprite = dot(250, 250);

addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	canvas.lock();   
	canvas.fillRect(canvas.rect, 0x000000);

	curve(p0, p1, p2, p3);
	canvas.unlock();
}


// all math from http://en.wikipedia.org/wiki/Cubic_Hermite_spline
function curve(p0:Sprite, p1:Sprite, p2:Sprite, p3:Sprite, rez:Number=.02):void{
	var px:Number = 0;
    var py:Number = 0;
	var m0:Point = tangent(p1, p0);
	var m1:Point = tangent(p2, p0);
	var m2:Point = tangent(p3, p1);
	var m3:Point = tangent(p3, p2);
	
	for (var t:Number = 0; t < 1; t+=rez){
		 var t_2:Number = t * t;
		 var _1_t:Number = 1 - t;
		 var _2t:Number = 2 * t;
		 
		 var h00:Number =  (1 + _2t) * (_1_t) * (_1_t);
		 var h10:Number =  t  * (_1_t) * (_1_t);
		 var h01:Number =  t_2 * (3 - _2t);
		 var h11:Number =  t_2 * (t - 1);
		 
		 px = h00 * p0.x + h10 * m0.x + h01 * p1.x + h11 * m1.x;
		 py = h00 * p0.y + h10 * m0.y + h01 * p1.y + h11 * m1.y;
		 canvas.setPixel(px, py, 0xFFFFFF);
		 
		 px = h00 * p1.x + h10 * m1.x + h01 * p2.x + h11 * m2.x;
		 py = h00 * p1.y + h10 * m1.y + h01 * p2.y + h11 * m2.y;
		 canvas.setPixel(px, py, 0xFFFFFF);
		 
		 px = h00 * p2.x + h10 * m2.x + h01 * p3.x + h11 * m3.x;
		 py = h00 * p2.y + h10 * m2.y + h01 * p3.y + h11 * m3.y;
		 canvas.setPixel(px, py, 0xFFFFFF);
	}
}

function tangent(pk1:Sprite, pk_1:Sprite){
	return new Point((pk1.x - pk_1.x) / 2, (pk1.y - pk_1.y) / 2);
}

// draggable dot
function dot(xp:Number, yp:Number, col:uint = 0x507399, rad:Number=5):Sprite {
	var s:Sprite = Sprite(addChild(new Sprite));
	s.x = xp;
	s.y = yp;
	with(s.graphics) beginFill(col), drawCircle(0,0,rad);
	s.buttonMode = true;
	s.addEventListener(MouseEvent.MOUSE_DOWN, onDrag);
	return s;
}
function onDrag(evt:MouseEvent):void {
	evt.currentTarget.startDrag()
}
stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
function onUp(evt:MouseEvent):void{
	stopDrag();
}

This is snippet draws a Catmull-Rom spline based on 4 control points. A nice feature of Catmull-Rom splines is that they always pass through each of their control points.


have a look at the swf here:

I first learned about Catmull-Rom splines somewhere in the processing forums.

I got all my info for this snippet from this wikipedia page.

There is a good deal of room for optimization here… I may post an optimized version in the future.

Cubic Hermite Spline

var canvas:BitmapData = new BitmapData(400,400,false, 0x000000);
addChild(new Bitmap(canvas));

// create some draggable dots
var p0:Sprite = dot(100, 100);
var p1:Sprite = dot(200, 200);
var m0:Sprite = dot(200, 100, 0xFFFFFF, 3)
var m1:Sprite = dot(100, 200, 0xFFFFFF, 3);

addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	canvas.fillRect(canvas.rect, 0x000000);
	var px:Number = 0;
	var py:Number = 0;
	for (var t:Number = 0; t <1; t+=.01){
		var t_2:Number = t * t;
		var t_3:Number = t_2 * t;
		
		// some repetitive math for clarity
		px = (2 * t_3  - 3 * t_2 + 1) * p0.x +(t_3 - 2 * t_2 + t) *
				 m0.x + (-2 * t_3 + 3 * t_2) * p1.x + (t_3 - t_2) * m1.x;
		py = (2 * t_3  - 3 * t_2 + 1) * p0.y +(t_3 - 2 * t_2 + t) *
				 m0.y + (-2 * t_3 + 3 * t_2) * p1.y + (t_3 - t_2) * m1.y;
		canvas.setPixel(px, py, 0xFF0000);
	}
}
// draggable dot
function dot(xp:Number, yp:Number, col:uint = 0x507399, rad:Number=5):Sprite {
	var s:Sprite = Sprite(addChild(new Sprite()));
	s.x = xp;
	s.y = yp;
	with(s.graphics) beginFill(col), drawCircle(0,0,rad);
	s.buttonMode = true;
	s.addEventListener(MouseEvent.MOUSE_DOWN, onDrag);
	return s;
}
function onDrag(evt:MouseEvent):void {
	evt.currentTarget.startDrag()
}
stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
function onUp(evt:MouseEvent):void{
	stopDrag();
}

This is the start of my exploration of Cubic Hermite Splines (like Catmull–Rom)… I wrote this snippet while skimming the wikipedia article on the subject…. at first I wasn’t sure if this was correct, but I added tangent calculations for a Catmull-Rom and it worked nicely… will post that tomorrow.

more(nesting(functions(graphDrawing)));

[SWF(width=800, height=600)]

var canvas:Graphics; 
var graphData:Array = sineData();

var graph0:Shape = Shape(addChild(new Shape()));
graph0.x = 50;
graph0.y = 150;

var graph1:Shape = Shape(addChild(new Shape()));
graph1.x = 400;
graph1.y = 150;

var graph2:Shape = Shape(addChild(new Shape()));
graph2.x = 50;
graph2.y = 400;

// use graphData to draw 3 different looking graphs:

canvas = graph0.graphics;
axis(lines(graphData));

canvas = graph1.graphics;
axis(dots(graphData, 0xFF0000), 0xFFCC00, 2);

canvas = graph2.graphics;
axis(dots(dots(lines(lines(graphData, 0xCCCCCC, 20))), 0x0022FF, 0, 4), 0xFF);


// generate data
function sineData():Array{
	var dat:Array = new Array();
	for (var i:int = 0; i<60; i++){
		dat.push(new Point(i * 4,  (30 + i) * Math.sin(i * 24 * Math.PI/180)));
	}            
	return dat;
}

// render lines
function lines(dat:Array, col:uint=0x000000, thick:Number=0):Array{
	canvas.lineStyle(thick, col);
	canvas.moveTo(dat[0].x, dat[0].y)
	for (var i:int = 1; i<dat.length; i++){
		 canvas.lineTo(dat[i].x, dat[i].y);
	}
	return dat;
}

// render dots
function dots(dat:Array, col:uint=0xFF0000, thick:Number=0, rad:Number=1.5):Array{
	canvas.lineStyle(thick, col);
	for (var i:int = 0; i<dat.length; i++){
		 canvas.drawCircle(dat[i].x, dat[i].y, rad);
	}
	return dat;
}

// render graph axis
function axis(dat:Array, col:uint=0x000000, thick:Number=0):Array{
	var d:Array = dat.concat();
	d.sortOn("y", Array.NUMERIC);
	var lastIndex:int = d.length - 1;
	var minY:Number = d[0].y;
	var maxY:Number = d[lastIndex].y;
	d.sortOn("x", Array.NUMERIC);
	var minX:Number = d[0].x;
	var maxX:Number = d[lastIndex].x;
	canvas.lineStyle(thick, col, .2);
	canvas.moveTo(minX, 0);
	canvas.lineTo(maxX, 0);
	canvas.lineStyle(thick, col);
	canvas.moveTo(minX, minY);
	canvas.lineTo(minX, maxY);
	canvas.lineTo(maxX, maxY);
	return dat;
}

This is something I’ve been meaning to post for awhile. Finally had time to write it today… It contains functions that are designed to be nested for the purpose of rendering a small data set in a few different ways…

The upper left image is rendered with axis labels and lines… and it defaults to the color black (line 21):

axis(lines(graphData));

The upper right image is rendered with yellow axis and red dots (line 24):

axis(dots(graphData, 0xFF0000), 0xFFCC00, 2);

etc… (line 27)
axis(dots(dots(lines(lines(graphData, 0xCCCCCC, 20))), 0x0022FF, 0, 4), 0xFF);

Alternatively you could write each function call on one line:

lines(graphData, 0xCCCCCC, 20);
lines(graphData);
dots(graphData);
dots(graphData, 0x0022FF, 0, 4)
axis(graphData, 0xFF);

NOTE: If you think this post is insane, please read the warning page of this site…

Torus 3D

stage.frameRate = 30;
const TWO_PI:Number = Math.PI * 2;
var centerX:Number = 200;
var centerY:Number = 200;
var p:Array = new Array();
var zpos:Number;
var xpos:Number;
var ypos:Number;
var depth:Number;
var canvas:BitmapData = new BitmapData(400,400,true,0xFF000000);
addChild(new Bitmap(canvas));

var dy:Number = 0
var dx:Number = 0;
 
addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	canvas.fillRect(canvas.rect, 0xFF000000);
	 
	 dx += (mouseX / 100 - dx)/12;
	 dy += (mouseY / 100 - dy)/12;
	 var xp:Number, yp:Number, zp:Number;
	 
	 canvas.lock();
	 for (var a:Number =0; a < TWO_PI; a+=.08){
		  for (var b:Number =0; b < TWO_PI; b+=.08){
			  xp = (70 + 40 * Math.cos(a)) * Math.cos(b) ;
			  yp = (70 + 40 * Math.cos(a)) * Math.sin(b);
			  zp =  40 * Math.sin(a);
			  calc3D(xp, yp, zp, dx, dy);
			  convert3D();
			  canvas.setPixel(p[0], p[1], 0xFFFFFF);
		  }
	 }
	 canvas.unlock();
}
function calc3D(px:Number, py:Number, pz:Number, rotX:Number=0, rotY:Number=0):void {
	// I first learned this from code by Andries Odendaal - www.wireframe.co.za
	zpos=pz*Math.cos(rotX)-px*Math.sin(rotX) ;
	xpos=pz*Math.sin(rotX)+px*Math.cos(rotX) ;
	ypos=py*Math.cos(rotY)-zpos*Math.sin(rotY) ;
	zpos=py*Math.sin(rotY)+zpos*Math.cos(rotY);
}
function convert3D():void {
	depth = 1/((zpos/200)+1);
	p[0] =  xpos * depth + centerX;
	p[1] =  ypos * depth + centerY;
}

This code draws a rotating 3D torus using setPixel().

Flashvars

// variable is passed into the swf via Javascript (most likely swfObject)
// myswf.swf?myFlashVar="a new value"

var myFlashVar:String = "Some Default Value";
if (root.loaderInfo.parameters.myFlashVar){
   myFlashVar = root.loaderInfo.parameters.myFlashVar;
}

Getting at flashvars in AS3 requires a bit more typing than it did in AS2. Above is a technique I find myself using from time to time. Usually for paths to xml or dynamic callouts (large buttons with text, an image and a link that change frequently).

Low Precision Sine and Cosine

var t:Number=0, cos:Number, sin:Number;

var canvas:BitmapData = new BitmapData(400,400,false, 0x000000);
addChild(new Bitmap(canvas));

var shape:Shape = new Shape();
with(shape.graphics) beginFill(0x568338, .2), drawCircle(0,0,10);

addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	
	     t += .1;
		// -- low precision sine/cosine
	    //always wrap input angle to -PI..PI
		if (t < -3.14159265){
			t += 6.28318531;
		}else{
		if (t >  3.14159265)
			t -= 6.28318531;
		}
		
		//compute sine
		if (t < 0){
			sin = 1.27323954 * t + .405284735 * t * t;
		}else{
			sin = 1.27323954 * t - 0.405284735 * t * t;
		}
		
		//compute cosine: sin(t + PI/2) = cos(t)
		t += 1.57079632;
		if (t >  3.14159265){
			t -= 6.28318531;
		}
		
		if (t < 0){
			cos = 1.27323954 * t + 0.405284735 * t * t
		}else{
			cos = 1.27323954 * t - 0.405284735 * t * t;
		}
		t -= 1.57079632;
		
		// move the shape
		shape.x = 200 + 100 * cos;
		shape.y = 200 + 100 * sin;
		
		// draw to the canvas
		canvas.draw(shape, shape.transform.matrix);
}

This snippet draws a circle using low precision sine and cosine… you’ll notice that its not a perfect looking circle:


Back in January I saw this blog post by Michael Baczynski over at http://lab.polygonal.de/. The blog post describes a technique for fast sine and cosine approximation – (I highly recommend giving it a read – very fun stuff).

It’s worth noting that there is a higher precision sine and cosine that will likely draw a better looking circle but will be about half as fast. According to the original post … the low precision technique is approximately 14x faster than using Math.cos()/Math.sin().

There some other really great posts over at polygonal go check them out.

Unique Array Comparison

function uniqueCompare(arr:Array, compare:Function):void {
	var leng:int = arr.length;
	for (var i:int = 0; i<leng; i++){
		for (var j:int = i + 1; j<leng; j++){
			compare(arr[i], arr[j]);
		}
	}
}

var numbers:Array = [1, 2, 3, 4, 5];

uniqueCompare(numbers, compareCallback);

function compareCallback(a:*, b:*):void {
	trace("compare " + a + " to " + b);
}

/*
outputs:
compare 1 to 2
compare 1 to 3
compare 1 to 4
compare 1 to 5
compare 2 to 3
compare 2 to 4
compare 2 to 5
compare 3 to 4
compare 3 to 5
compare 4 to 5
*/

This snippet shows a function called uniqueCompare() that compares all elements in an array to all other elements in an array without repeating a comparison.

Object.constructor()

var Box:Function = {
	constructor : function(color:uint, w:Number, h:Number):void {
		this.color = color;
		this.s = new Shape();
		with(this.s.graphics) beginFill(this.color), drawRect(0,0,w,h);
		addChild(this.s);
		
		this.setLoc = function(x:Number, y:Number):void{
			this.s.x = x;
			this.s.y = y;
	   }
	}
}.constructor;

var box0:Object = new Box(0xFF0000, 100, 100);

box0.setLoc(100, 10);

var box1:Object = new Box(0x000000, 50, 50);

box1.setLoc(210, 10);

This snippet makes use of Object.constructor() to allow creation of Object instances using the new keyword. This is for fun only, I don’t recommend this over actual Classes.

Circle of Particles #2

[SWF(width = 400, height = 400)];
var canvas:BitmapData = new BitmapData(400,400, false, 0x000000);
var eraser:BitmapData = new BitmapData(400,400, true, 0x11000000);
addChild(new Bitmap(canvas));

var particles:Array = new Array();
for (var i:int = 0; i < 500; i++){
	particles.push(makeParticle());
}

addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	canvas.copyPixels(eraser, eraser.rect, new Point(0,0), null, null, true);
	for (var i:int = 0; i < particles.length; i++){
		particles[i]();
	}
}

function makeParticle():Function {
	var dx:Number, dy:Number;
	var x:Number = 200;
	var y:Number = 200;
	var vx:Number = Math.random() * 4 - 2;
	var vy:Number = Math.random() * 4 - 2;
	var ang:Number;
	return function():void {
			  x += vx;
		      y += vy;
		      dx = 200 - x;
		      dy=  200 - y;
			   if (Math.sqrt((dx * dx) + (dy * dy)) > 130){
				ang = Math.atan2(dy, dx) / Math.PI * 180;
				 vx = Math.cos(ang);
				 vy = Math.sin(ang);
			   }
		       canvas.setPixel(x, y, 0xFFFFFF);
	}
}

This is an interesting variation on yesterdays post. The result will looks like this…


Click to view swf…

I also decided to do a processing port of this with 3,000 particles:

Processing Version

The processing version eventually evolved into this:


Click for enlarged version…

Circle of Particles

var canvas:BitmapData = new BitmapData(400,400, false, 0xFFFFFF);
addChild(new Bitmap(canvas));
var eraser:BitmapData = new BitmapData(400,400, true, 0x33FFFFFF);

var particles:Array = new Array();
for (var i:int = 0; i < 1000; i++){
	particles.push(makeParticle());
}

addEventListener(Event.ENTER_FRAME, onLoop);
function onLoop(evt:Event):void {
	canvas.copyPixels(eraser, eraser.rect, new Point(0,0), null, null, true);
	for (var i:int = 0; i < particles.length; i++){
		particles[i]();
	}
}

function makeParticle():Function {
	var dx:Number, dy:Number;
	var x:Number = 200;
	var y:Number = 200;
	var vx:Number = Math.random() * 4 - 2;
	var vy:Number = Math.random() * 4 - 2;
	return function():void {
			  x += vx;
		      y += vy;
		      dx = x - 200;
		      dy= y - 200;
			   if (Math.sqrt((dx * dx) + (dy * dy)) > 100){
				 vx *= -1;
				 vy *= -1;
			   }
		       canvas.setPixel(x, y, 0x000000);
	}
}

This creates a circular area filled with moving particles/pixels. It makes use of yesterdays functional programming techniques.