123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- var Magic = function (options) {
- options = options || {};
- // Array to hold all active spells being drawn
- this.spells = [];
-
- // When on, wait a certain amount of time before casting (for initial
- // load-up)
- this.counting = options.counting || true;
-
- // The amount of time to wait (in number of animate() calls)
- this.waitTime = options.waitTime || 20;
-
- // How fast to draw the spells
- this.step = options.step || 0.2;
-
- // Limit of branches per spell at any time
- this.prune_to = options.prune_to || 20;
-
- // Max duration possible (in steps) of a spell
- this.maxDuration = options.maxDuration || 600;
-
- // Min duration possible (in steps) of a spell
- this.minDuration = options.minDuration || 50;
-
- // Max angle deviation during branch split
- this.maxAngleChange = options.maxAngleChange || 180;
-
- // Min angle deviation during branch split
- this.minAngleChange = options.minAngleChange || 90;
-
- // Average length of new spline (branch)
- this.splineLength = options.splineLength || 15;
-
- // Whether new spells are cast with a new random color
- this.randomColored = options.randomColored || true;
-
- this.paused = false;
-
- // Save this query here now that the page is loaded
- this.canvas = document.getElementById("magic");
- };
-
-
- /* Get the height and width of full document. To avoid browser
- * incompatibility issues, choose the maximum of all height/width values.
- *
- * Method from http://stackoverflow.com/a/1147768 */
- Magic.prototype.getDocumentDimensions = function () {
- var body = document.body,
- html = document.documentElement;
-
- var height = Math.max(body.scrollHeight, body.offsetHeight,
- html.clientHeight, html.scrollHeight,
- html.offsetHeight);
-
- var width = Math.max(body.scrollWidth, body.offsetWidth,
- html.clientWidth, html.scrollWidth,
- html.offsetWidth);
-
- return {"height": height, "width": width};
- };
-
- /* Specifies what will be added to the red, green, and blue values of the
- * color at each interval. */
- Magic.prototype.pickGradient = function () {
- var directions = [-1, 0, 1];
- var magnitudes = [1, 2, 3];
-
- return {"r": directions[Math.floor(Math.random() * 3)] *
- magnitudes[Math.floor(Math.random() * 3)],
- "g": directions[Math.floor(Math.random() * 3)] *
- magnitudes[Math.floor(Math.random() * 3)],
- "b": directions[Math.floor(Math.random() * 3)] *
- magnitudes[Math.floor(Math.random() * 3)]};
- };
-
- /* Alters the given color by the given gradient and returns both.
-
- If the red, green, or blue value of the color is at 0 or 255 and the
- gradient for that value is not zero, then the gradient for that value
- will change signs.
-
- Unused for now. It turns out that changing the canvas stroke color is a
- costly operation and slows things down a lot. */
- Magic.prototype.nextColor = function(color, gradient) {
- var values = ["r", "g", "b"];
-
- // Check if color at end of range and reverse gradient direction if so
- for (var i = 0; i < 3; i++) {
- var colorValue = color[values[i]];
- var gradientValue = gradient[values[i]];
- if ((colorValue === 255 && gradientValue > 0) ||
- (colorValue === 0 && gradientValue < 0)) {
- gradient[values[i]] = gradientValue * -1;
- }
- }
-
- // Modify the color according to the gradient
- for (i = 0; i < 3; i++) {
- color[values[i]] = color[values[i]] + gradient[values[i]];
- }
-
- return {"color": color, "gradient": gradient};
- };
-
- /* Choose a random RGB color to begin the color gradient with */
- Magic.prototype.pickRandomColor = function() {
- return {"r": Math.floor(Math.random() * (255 + 1)),
- "g": Math.floor(Math.random() * (255 + 1)),
- "b": Math.floor(Math.random() * (255 + 1))};
- };
-
- Magic.prototype.clear = function() {
- this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
- }
-
- Magic.prototype.drawSplineSegment = function(branch, context) {
- var ax = (-branch.points[0].x + 3*branch.points[1].x - 3*branch.points[2].x + branch.points[3].x) / 6;
- var ay = (-branch.points[0].y + 3*branch.points[1].y - 3*branch.points[2].y + branch.points[3].y) / 6;
- var bx = (branch.points[0].x - 2*branch.points[1].x + branch.points[2].x) / 2;
- var by = (branch.points[0].y - 2*branch.points[1].y + branch.points[2].y) / 2;
- var cx = (-branch.points[0].x + branch.points[2].x) / 2;
- var cy = (-branch.points[0].y + branch.points[2].y) / 2;
- var dx = (branch.points[0].x + 4*branch.points[1].x + branch.points[2].x) / 6;
- var dy = (branch.points[0].y + 4*branch.points[1].y + branch.points[2].y) / 6;
- context.moveTo(
- ax*Math.pow(branch.t, 3) + bx*Math.pow(branch.t, 2) + cx*branch.t + dx,
- ay*Math.pow(branch.t, 3) + by*Math.pow(branch.t, 2) + cy*branch.t + dy
- );
- context.lineTo(
- ax*Math.pow(branch.t+this.step, 3) + bx*Math.pow(branch.t+this.step, 2) + cx*(branch.t+this.step) + dx,
- ay*Math.pow(branch.t+this.step, 3) + by*Math.pow(branch.t+this.step, 2) + cy*(branch.t+this.step) + dy
- );
- branch.t += this.step;
- };
-
- Magic.prototype.splitBranch = function(branch) {
- var newBranches = [];
- // Replace with 2 new branches
- for (var k = 0; k < 2; k++) {
-
- // Generate random deviation from previous angle
- var angle = branch.angle - (Math.random() * this.maxAngleChange - this.minAngleChange);
-
- // Generate random length
- var length = Math.random() * this.splineLength + 4;
-
- // Calculate new point
- var x2 = branch.points[3].x + Math.sin(Math.PI * angle / 180) * length;
- var y2 = branch.points[3].y - Math.cos(Math.PI * angle / 180) * length;
-
- // Add to new branch array
- newBranches.push({
- points:new Array(
- branch.points[1],
- branch.points[2],
- branch.points[3],
- {x:x2, y:y2}
- ),
- angle:angle,
- t:0,
- });
-
- }
- return newBranches;
- };
-
- Magic.prototype.cast = function(x, y, angle, chain) {
- // Find random position if not defined
- if (x === undefined) {
- x = Math.floor(Math.random() * (this.canvas.width + 1));
- }
- if (y === undefined) {
- y = Math.floor(Math.random() * (this.canvas.height + 1));
- }
- if (angle === undefined) {
- angle = Math.random() * 360;
- }
-
- var colorString = "rgb(10,10,10)";
- if (this.randomColored) {
- var color = this.pickRandomColor();
- colorString = "rgb(" + color.r + "," + color.g + "," + color.b + ")";
- }
-
- // Create new spell (branch)
- this.spells.push({
- branches:new Array({
- points:new Array({x:x, y:y}, {x:x, y:y}, {x:x, y:y}, {x:x, y:y}),
- angle:angle,
- t:0,
- }),
- color:colorString,
- duration:Math.floor(Math.random() * (this.maxDuration - this.minDuration + 1)) + 50,
- chain:chain,
- });
- };
-
- /* Most of this funtion is provided by Maissan Inc. in their tutorial:
- http://www.maissan.net/articles/simulating-vines */
- Magic.prototype.drawSpells = function(context, sort, prune, chain) {
- var AnimationFrame = window.AnimationFrame;
- AnimationFrame.shim();
- var animationFrame = new AnimationFrame(30),
- timeCounter = 0,
- color,
- gradient;
- context.lineWidth = 0.5;
-
- var self = this;
- function animate(time) {
- // resize canvas if document size changed
- dimensions = self.getDocumentDimensions();
- if ((dimensions.height !== self.canvas.height) ||
- (dimensions.width !== self.canvas.width)) {
- self.canvas.height = dimensions.height;
- self.canvas.width = dimensions.width;
- }
-
- if (!self.paused) {
- // if enough time has passed, cast another spell to draw
- if (timeCounter >= self.waitTime && self.counting) {
- self.cast(undefined, undefined, undefined, chain); // start position
- self.counting = false;
- }
-
- // Draw branches
- for (var i in self.spells) {
- context.beginPath();
- context.strokeStyle = self.spells[i].color;
-
- if (self.spells[i].duration > 0) {
- for (var j in self.spells[i].branches) {
- self.drawSplineSegment(self.spells[i].branches[j], context);
-
- // When finished drawing splines, create a new set of branches
- if (self.spells[i].branches[j].t >= 1) {
-
- var newBranches = self.splitBranch(self.spells[i].branches[j]);
-
- // Replace old branch with two new branches
- self.spells[i].branches.splice(j, 1);
- self.spells[i].branches = self.spells[i].branches.concat(newBranches);
- }
- }
-
- // If over 10 branches, prune the branches
- if (prune) {
- if (sort) {
- while (self.spells[i].branches.length > self.prune_to) {
- self.spells[i].branches.pop();
- }
- } else {
- while (self.spells[i].branches.length > self.prune_to) {
- self.spells[i].branches.splice(
- Math.floor(Math.random() * self.spells[i].branches.length),
- 1);
- }
- }
- }
-
- context.stroke();
- context.closePath();
- self.spells[i].duration--;
-
- } else {
- // cast a new spell at random position
- if (self.spells[i].chain) {
- self.cast(undefined, undefined, undefined, true);
- }
- self.spells.splice(i, 1); // spell is done now
- }
- }
-
- if (self.counting) {
- timeCounter++;
- }
- }
- frameId = animationFrame.request(animate);
- }
-
- // Drawing interval
- var frameId = animationFrame.request(animate);
- return frameId;
- };
-
- Magic.prototype.canvasClickHandler = function(event) {
- var x = event.pageX;
- var y = event.pageY;
-
- this.cast(x, y, undefined, false);
- };
-
- Magic.prototype.draw = function() {
- var interval_time = 2000;
-
- // Initialize canvas
- var dimensions = this.getDocumentDimensions();
- this.canvas.height = dimensions.height;
- this.canvas.width = dimensions.width;
-
- // Check for canvas support, bind click event, and get context
- if (this.canvas.getContext){
- var self = this;
- this.canvas.addEventListener("click", function () { self.canvasClickHandler.apply(self, arguments); });
-
- this.context = this.canvas.getContext("2d");
-
- // Cast magic spells
- var frameId = this.drawSpells(this.context, false, true, true);
- }
- };
|