Sierpinski Gasket

The Sierpinski Sierpinski gasket is a fractal named after Wacław Sierpiński. It is one of the basic examples of self-similar sets.

I built this simple Sierpinsi gasket render as a practice excercise while learning JavaScript from Douglas Crockford's JavaScript: The Good Parts

The code is below

$(function() {
	if($('#sierpinski').length <= 0 || $('sierpinski').hasClass('sierpinskied')) { return; }

	var jcanvas = $('#sierpinski');
	jcanvas.addClass('sierpinskied'); 

	var canvas = jcanvas[0]; // jQuery gets us an array, but we need the actual object

	var ctx = canvas.getContext('2d'); 


	// OOH - This is some baddy badness. 
	// _EQUITRIANGLE is now GLOBAL so that the timer can get to it. Nasty.
	_EQUITRIANGLE = equilateral_triangle(ctx, 450, 10, 10);

	// _R_DEPTH is also a global. EEugh.
	_R_DEPTH = 1;

	// Render the first triangle	
	_EQUITRIANGLE.draw('black');

	// Callback to re-render every 500ms
	setTimeout("sierpinski_timer(_EQUITRIANGLE);", 300);

});

// This function generates a nice randonm number between 0 and n
// @param n the highest value that can be chosen
sierpinski_rand = function(n) {
  return ( Math.floor ( Math.random ( ) * n + 1 ) );
}

// Callback to re-render the triangle and run the recursion
sierpinski_timer = function(tri) {

	// HORRIBLE global	
	if(_R_DEPTH >= 6) {
		_R_DEPTH = 1;
	} else {
		_R_DEPTH = _R_DEPTH + 1;
	}

	var r_c1 = sierpinski_rand(255);
	var r_c2 = sierpinski_rand(255);

	tri.sierpinskiRender( 'rgb('+r_c1+',0,0)', 'rgb(0,'+r_c2+',0)', _R_DEPTH);

	// I think the ; has to be in the Timeout because it gets evaled and the js 
	// engine will insert the ";" if its not there. 
	setTimeout("sierpinski_timer(_EQUITRIANGLE);", 300); 
}

// Create an equilateral_triangle object that inherits from triangle (see below)
// using functional inheritance technique
//
// All this object does is render an equilateral triangle from a given length
var equilateral_triangle = function(ctx, length, offset_x, offset_y) {

	// Height of equilateral triangle (sqrt3)s/2 = h 
	var height = Math.sqrt(3) * (length / 2);

	var points = [ 
		{x: offset_x, y: offset_y + height}, 
		{x: offset_x + length, y: offset_y + height}, 
		{x: offset_x + (length / 2), y: offset_y}
	];

	var that = triangle(ctx, points); // "Inherit" from triangle

	return that;
}

// Create a triangle object using functional inheritance technique
var triangle = function(ctx, points) {

	var that = {}; // We start with a blank object

	// I lifted this from the HTML5 specs
	// I think my app will break on IE :-/
	that.draw = function(color) {
		if(color === undefined) { color = 'black'; }
		ctx.fillStyle = color;
  	ctx.beginPath();  
	  ctx.moveTo(points[0].x,points[0].y);  
  	ctx.lineTo(points[1].x,points[1].y);  
  	ctx.lineTo(points[2].x,points[2].y);  
  	ctx.fill();  
	}

	// Here Im defining the sierpinski rendering routine.
  // This gets called recursively up to max_recursion times.
  // This way the calculating of the child triangles can always be
	// in reference to the parent triangle's points
	that.sierpinskiRender = function(color, hole_color, max_recursion, depth) {

		if(max_recursion === undefined) { max_recursion = 2; }
		if(depth === undefined) { depth = 0; }

		depth = depth + 1; // Crockford says dont use ++ ... so I wont

		// Overwite the parent with a base color so we can see the kids
		that.draw(hole_color); 
	
		// Wikipedia shows a better functional way to render this... but my 
		// simple way is ok for this 
		// learning excercise.

		// I set up three new triangles, and then calculate their points based
		// on the parent triangle's original points
	
		// Im always starting at the bottom left point of the triangle,
		// moving to the bottom right,
		// then going to the apex.
    
		// The second and third points in this are calculated using the 
		// midpoint theorem
		var new_points_alpha = [
			{x: points[0].x, y: points[0].y}, 
			{x: (points[0].x + points[1].x) / 2, y: (points[0].y + points[1].y) / 2},
			{x: (points[0].x + points[2].x) / 2, y: (points[0].y + points[2].y) / 2} 
		];

		// Render the first child	
		var internalTriangle_alpha = triangle(ctx, new_points_alpha);
		internalTriangle_alpha.draw(color);
		
		// The first point has been calculated using the midpoint already... so use that 
		// The third points in this is calculated using the midpoint theorem
		var new_points_beta = [
			{x: new_points_alpha[1].x, y: new_points_alpha[1].y}, 
			{x: points[1].x, y: points[1].y},
			{x: (points[1].x + points[2].x) / 2, y: (points[1].y + points[2].y) / 2} 
		];
	
		// Render the second child	
		var internalTriangle_beta = triangle(ctx, new_points_beta);
		internalTriangle_beta.draw(color);

		// All the points have now been calculated so we can just choose the right ones
		var new_points_charlie = [
			{x: new_points_alpha[2].x, y: new_points_alpha[2].y}, 
			{x: new_points_beta[2].x, y: new_points_beta[2].y}, 
			{x: points[2].x, y: points[2].y} 
		];
	
		// Render the third child	
		var internalTriangle_charlie = triangle(ctx, new_points_charlie);
		internalTriangle_charlie.draw(color);

		if(depth <= max_recursion) {
			internalTriangle_alpha.sierpinskiRender(color, hole_color, max_recursion, depth);
			internalTriangle_beta.sierpinskiRender(color, hole_color, max_recursion, depth);
			internalTriangle_charlie.sierpinskiRender(color, hole_color, max_recursion, depth);
		}

	}

	return that;

};
blog comments powered by Disqus