// Canvas Kaleidoscope -- Kent Brewster, 2009 // http://kentbrewster.com/collide-o-scope (function (w, d) { var trueName = 'COLLIDE_O_SCOPE'; w[trueName] = {}; var $ = w[trueName]; $.d = d; $.w = w; $.f = function() { return { behavior : function() { // an array to hold all circles $.v = []; // initial make and draw for (var i = 0; i < $.a.count; i++) { $.f.makeCircle(i); } // we'll try for 30 frames per second setInterval(trueName + ".f.draw()", 33); }, makeCircle : function(i, x, y) { // each circle has its own object member of the master $.v array $.v[i] = {}; // initial X location if (!x) { // random $.v[i].x = $.f.getRnd($.a.width); } else { // this circle was added by viewer click $.v[i].x = x; } // initial X vector, 1 to 6 pixels per turn $.v[i].dx = $.f.getRnd(6, 1); // initial Y location if (!y) { // random $.v[i].y = $.f.getRnd($.a.height); } else { // this circle was added by viewer click $.v[i].y = y; } // initial Y vector, 1 to 6 pixels per turn $.v[i].dy = $.f.getRnd(6, 1); // number of rings in this circle var n = $.f.getRnd($.a.addSize, $.a.baseSize); // array to hold rings $.v[i].c = []; for (var j = 0; j < n; j++) { // object to hold colors for each ring $.v[i].c[j] = {}; // red $.v[i].c[j].r = $.f.getRnd($.a.maxColor); // green $.v[i].c[j].g = $.f.getRnd($.a.maxColor); // blue $.v[i].c[j].b = $.f.getRnd($.a.maxColor); } }, getRnd: function(r, m) { // no range? use 1 if (!r) { r = 1; } // no minimum? use 0 if (!m) { m = 0; } // return a random number between 0 and the specified range, plus the minimum, rounded down return Math.floor(Math.random() * r) + m; }, draw: function() { // set our background color $.c.fillStyle = 'rgba(' + $.a.backgroundRed + ',' + $.a.backgroundGreen + ',' + $.a.backgroundBlue + ',' + $.a.backgroundAlpha + ')'; // draw our background $.c.fillRect(0,0,$.a.width,$.a.height); // get length of circle array var n = $.v.length; // draw visible circles for (var i = $.a.firstCirc; i < n; i++) { var g = $.a.grid * 5; var ix = parseInt($.v[i].x / g, 10); var iy = parseInt($.v[i].y / g, 10); // skanky punk-rock collision detection for (var j = $.a.firstCirc; j < n; j++) { if (i !== j) { var jx = parseInt($.v[j].x / g, 10); var jy = parseInt($.v[j].y / g, 10); // are both numbers within g pixels of each other? if (ix === jx && iy === jy) { // change directions $.v[i].dx = $.f.getRnd(6, 1); $.v[i].dy = $.f.getRnd(6, 1); $.v[j].dx = $.f.getRnd(6, 1); $.v[j].dy = $.f.getRnd(6, 1); } } } // offscreen? wrap around $.v[i].x = ($.v[i].x + $.v[i].dx) % $.a.width; $.v[i].y = ($.v[i].y + $.v[i].dy) % $.a.height; // draw all rings in this circle for (var k = 1; k < $.v[i].c.length; k++) { $.f.drawRings({ "x":$.v[i].x, "y":$.v[i].y, "z":(k * ($.a.grid/2) - 5), "r":$.v[i].c[k].r, "g":$.v[i].c[k].g, "b":$.v[i].c[k].b, "a":$.a.ringAlpha }); } } }, drawRings : function(c) { $.c.fillStyle = 'rgba(' + c.r + ',' + c.g + ',' + c.b + ',' + c.a + ')'; // draw all eight possible reflections of this circle $.f.circ(c.x, c.y, c.z); $.f.circ($.a.width - c.x, c.y, c.z); $.f.circ(c.x, $.a.height - c.y, c.z); $.f.circ($.a.width - c.x, $.a.height - c.y, c.z); $.f.circ(c.y, c.x, c.z); $.f.circ($.a.width - c.y, c.x, c.z); $.f.circ(c.y, $.a.height - c.x, c.z); $.f.circ($.a.width - c.y, $.a.height - c.x, c.z); }, circ : function(x, y, r) { $.c.beginPath(); // 0 = start, $.a.p = 2 x pi, for full circle $.c.arc(x, y, r, 0, $.a.p, true); $.c.closePath(); $.c.fill(); }, parseJson : function(json) { this.parseJson.data = json; if ( typeof json !== 'string') { return {"err":"trying to parse a non-string JSON object"}; } try { var f = Function(['var alert,document,top,self,window,parent,Number,Date,Object,Function,', 'Array,String,Math,RegExp,Image,ActiveXObject;', 'return (' , json.replace(/<\!--.+-->/gim,'').replace(/\bfunction\b/g,'function­') , ');'].join('')); return f(); } catch (e) { return {"err":"trouble parsing JSON object; running with defaults"}; } }, up : function(v) { var e = v || window.event; var t = $.v.length; // starting X and Y are where you clicked $.f.makeCircle(t, e.clientX - $.x, e.clientY - $.y); // keep the number of circles on the screen constant $.a.firstCirc++; }, houseKeep: function(s){ // arg store $.a = {}; // were any args sent from the script? if (s.innerHTML) { $.a = $.f.parseJson(s.innerHTML); } // load some defaults var defaults = { "p":Math.PI * 2, "height":400, "width":400, "count":10, "addSize":5, "baseSize":4, "grid":15, "maxColor":256, "backgroundRed":0, "backgroundGreen":0, "backgroundBlue":0, "backgroundAlpha":1, "ringAlpha":".1" }; for (var d in defaults) { if ($.a[d] === undefined) { $.a[d] = defaults[d]; } } // initially we draw all the circles $.a.firstCirc = 0; // make our canvas var c = $.d.createElement("CANVAS"); c.id = trueName; c.height = $.a.height; c.width = $.a.width; s.parentNode.insertBefore(c, s); $.f.getCanvasPosition(c); $.c = c.getContext('2d'); // if the viewer clicks, do something c.onmouseup = $.f.up; s.parentNode.removeChild(s); // if the viewer resizes the window, re-check to get canvas position $.w.onresize = function(){ $.f.getCanvasPosition($.d.getElementById(trueName)); }; $.f.behavior(); }, getCanvasPosition : function(p) { $.x = 0; $.y = 0; do { $.x += p.offsetLeft; $.y += p.offsetTop; p = p.offsetParent; } while (p.offsetParent); }, init : function(thisScript) { var s = d.getElementsByTagName('SCRIPT'); for (var i = 0; i < s.length; i++) { if (s[i].src.match(thisScript)) { $.f.houseKeep(s[i]); break; } } } }; }(); var thisScript = /collide.js$/; if(typeof w.addEventListener !== 'undefined') { w.addEventListener('load', function() { $.f.init(thisScript); }, false); } else if(typeof w.attachEvent !== 'undefined') { w.attachEvent('onload', function() { $.f.init(thisScript); }); } })(window, document);