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
$(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;
};