HTML5 Mandelbrot Set w/ Realtime Julia Set Explorer
Programmed in 225 lines of Javascript Code by Chris Sunami
var M = (function ($) {
"use strict";
var constants = {
testRadius: 2,
maxCycles: 100
},
properties = {
grid: {
center: {r: -0.5, i: 0},
resolution: {r: 320, i: 320},
zoom: {r: 0.75, i: 0.75},
dim: {r: {min: 0, max: 0}, i: {min: 0, max: 0}},
iter: {r: 0, i: 0}
},
display: {
currentType: 'M',
M: {
$canvas: $("<canvas/>"),
img: null,
context: null,
type: 'M',
offset: 0
},
J: {
$canvas: $("<canvas/>"),
img: null,
context: null,
type: 'J',
offset: 0,
cfactor: {r: 0, i: 0}
}
},
color: {
}
},
methods = (function () {
var math = (function () {
var complexSquare = function (n) {
return {r: n.r * n.r - n.i * n.i, i: 2 * n.r * n.i};
},
complexAdd = function (n1, n2) {
return {r: n1.r + n2.r, i: n1.i + n2.i};
},
complexTestAgainstRadius = function (n, rad) {
return (n.r * n.r + n.i * n.i) > rad * rad;
},
iterate = function (n, test) {
return complexAdd(complexSquare(n), test);
};
return {test: complexTestAgainstRadius, iterate: iterate};
}()),
display = (function (cd, pg) {
var toDisplay = function (n) {
return {
r: Math.round((n.r - pg.center.r) * pg.zoom.r * pg.resolution.r / 2 + pg.resolution.r / 2),
i: Math.round((n.i - pg.center.i) * pg.zoom.i * pg.resolution.i / 2 + pg.resolution.i / 2)
};
},
fromDisplay = function (n) {
return {
r: (n.r - pg.resolution.r / 2) * 2 / (pg.zoom.r * pg.resolution.r) + pg.center.r,
i: (n.i - pg.resolution.i / 2) * 2 / (pg.zoom.i * pg.resolution.i) + pg.center.i
};
},
getPixel = function (point) {
var pt = toDisplay(point);
return 4 * (pt.i * pg.resolution.r + pt.r);
};
return {
getPixel: getPixel,
fromDisplay: fromDisplay
};
}(constants.display, properties.grid)),
color = (function (pd) {
var plotPixel = function (pixel, hue) {
var pdx = pd[pd.currentType];
pdx.img.data[pixel] = hue.r;
pdx.img.data[pixel + 1] = hue.g;
pdx.img.data[pixel + 2] = hue.b;
pdx.img.data[pixel + 3] = hue.a;
},
tint = function (value) {
if (value < 10) {
return {r: value * 10, g: 0, b: value, a: 180};
}
return {r: 0, g: value, b: value * 10, a: 180};
},
plotPoint = function (point, value) {
var pixel = display.getPixel(point);
plotPixel(pixel, tint(value));
};
return plotPoint;
}(properties.display)),
construction = (function (pg, c) {
var grid = {
update: (function () {
var dim = function () {
pg.dim.r.min = pg.center.r - 1 / pg.zoom.r;
pg.dim.r.max = pg.center.r + 1 / pg.zoom.r;
pg.dim.i.min = pg.center.i - 1 / pg.zoom.i;
pg.dim.i.max = pg.center.i + 1 / pg.zoom.i;
},
iter = function () {
pg.iter.r = 2 / (pg.zoom.r * pg.resolution.r);
pg.iter.i = 2 / (pg.zoom.i * pg.resolution.i);
},
center = function (center) {
pg.center = center;
dim();
},
resolution = function (resolution) {
pg.resolution = resolution;
iter();
},
zoom = function (zoom) {
pg.zoom = zoom;
dim();
iter();
},
init = function () {
dim();
iter();
};
return {center: center, resolution: resolution, zoom: zoom, init: init};
}()),
run: function (callback) {
var x,
y;
for (x = pg.dim.r.min; x <= pg.dim.r.max; x += pg.iter.r) {
for (y = pg.dim.i.min; y <= pg.dim.i.max; y += pg.iter.i) {
callback({r: x, i: y});
}
}
}
},
pointTest = function (orbitPoint) {
var iter,
cfactor,
pd = properties.display;
switch (pd.currentType) {
case "M":
cfactor = orbitPoint;
break;
case "J":
cfactor = pd.J.cfactor;
break;
}
for (iter = 0; iter <= c.maxCycles; iter += 1) {
orbitPoint = math.iterate(orbitPoint, cfactor);
if (math.test(orbitPoint, c.testRadius)) {
return iter;
}
}
return iter;
},
plot = function (testPoint) {
var cycles = pointTest(testPoint);
color(testPoint, cycles);
};
return {run: grid.run, update: grid.update, plot: plot};
}(properties.grid, constants)),
screen = (function (pd, pg) {
var createCanvas = function (pdx) {
pdx.context = pdx.$canvas[0].getContext("2d");
pdx.$canvas.width(pg.resolution.r);
pdx.$canvas.height(pg.resolution.i);
pdx.$canvas[0].width = pg.resolution.r;
pdx.$canvas[0].height = pg.resolution.i;
pdx.img = pdx.context.createImageData(pg.resolution.r, pg.resolution.i);
$("h1").after(pdx.$canvas);
},
init = function () {
createCanvas(pd.J);
createCanvas(pd.M);
pd.M.offset = pd.M.$canvas.offset();
};
return init;
}(properties.display, properties.grid)),
test = (function (pdx) {
var gridSpec = function (point) {
color(point, 80);
pdx.context.putImageData(pdx.img, 0, 0);
},
run = function () {
construction.run(gridSpec);
};
return run;
}(properties.display.M)),
fractal = (function (pd) {
var run = function (pdx) {
pd.currentType = pdx.type;
construction.run(construction.plot);
pdx.context.putImageData(pdx.img, 0, 0);
},
init = function () {
construction.update.init();
screen();
run(pd.M);
run(pd.J);
};
return {init: init, run: run};
}(properties.display)),
juliaTracker = (function (pd) {
var init = function () {
pd.M.$canvas.on("mousemove", function (e) {
pd.J.cfactor = display.fromDisplay({
r: e.pageX - pd.M.offset.left,
i: e.pageY - pd.M.offset.top
});
fractal.run(pd.J);
});
};
return init;
}(properties.display));
return {init: fractal.init, run: fractal.run, update: construction.update, julia: juliaTracker, test: test};
}());
return {
init: methods.init,
run: methods.run,
update: methods.update,
properties: properties.grid,
test: methods.test,
julia: methods.julia
};
}(jQuery));