Refactored and optimized magic

This commit is contained in:
Tyler Hallada 2014-07-19 22:42:43 -04:00
parent 2fa00b2929
commit 208a20f06f
3 changed files with 143 additions and 122 deletions

View File

@ -24,6 +24,7 @@
<link href='http://fonts.googleapis.com/css?family=Open+Sans:400,400italics,300,300italics,200' rel='stylesheet' type='text/css'> <link href='http://fonts.googleapis.com/css?family=Open+Sans:400,400italics,300,300italics,200' rel='stylesheet' type='text/css'>
<!-- Scripts --> <!-- Scripts -->
<script src="js/AnimationFrame.min.js"></script>
<script async src="js/magic.js"></script> <script async src="js/magic.js"></script>
</head> </head>
<body> <body>

8
js/AnimationFrame.min.js vendored Normal file
View File

@ -0,0 +1,8 @@
/**
* An even better animation frame.
*
* @copyright Oleg Slobodskoi 2013
* @website https://github.com/kof/animationFrame
* @license MIT
*/
(function(window){"use strict";var nativeRequestAnimationFrame,nativeCancelAnimationFrame;(function(){var i,vendors=["webkit","moz","ms","o"],top;try{window.top.name;top=window.top}catch(e){top=window}nativeRequestAnimationFrame=top.requestAnimationFrame;nativeCancelAnimationFrame=top.cancelAnimationFrame||top.cancelRequestAnimationFrame;for(i=0;i<vendors.length&&!nativeRequestAnimationFrame;i++){nativeRequestAnimationFrame=top[vendors[i]+"RequestAnimationFrame"];nativeCancelAnimationFrame=top[vendors[i]+"CancelAnimationFrame"]||top[vendors[i]+"CancelRequestAnimationFrame"]}nativeRequestAnimationFrame&&nativeRequestAnimationFrame(function(){AnimationFrame.hasNative=true})})();function AnimationFrame(options){if(!(this instanceof AnimationFrame))return new AnimationFrame(options);options||(options={});if(typeof options=="number")options={frameRate:options};options.useNative!=null||(options.useNative=true);this.options=options;this.frameRate=options.frameRate||AnimationFrame.FRAME_RATE;this._frameLength=1e3/this.frameRate;this._isCustomFrameRate=this.frameRate!==AnimationFrame.FRAME_RATE;this._timeoutId=null;this._callbacks={};this._lastTickTime=0;this._tickCounter=0}AnimationFrame.FRAME_RATE=60;AnimationFrame.shim=function(options){var animationFrame=new AnimationFrame(options);window.requestAnimationFrame=function(callback){return animationFrame.request(callback)};window.cancelAnimationFrame=function(id){return animationFrame.cancel(id)};return animationFrame};AnimationFrame.now=Date.now||function(){return(new Date).getTime()};AnimationFrame.navigationStart=AnimationFrame.now();AnimationFrame.perfNow=function(){if(window.performance&&window.performance.now)return window.performance.now();return AnimationFrame.now()-AnimationFrame.navigationStart};AnimationFrame.hasNative=false;AnimationFrame.prototype.request=function(callback){var self=this,delay;++this._tickCounter;if(AnimationFrame.hasNative&&self.options.useNative&&!this._isCustomFrameRate){return nativeRequestAnimationFrame(callback)}if(!callback)throw new TypeError("Not enough arguments");if(this._timeoutId==null){delay=this._frameLength+this._lastTickTime-AnimationFrame.now();if(delay<0)delay=0;this._timeoutId=window.setTimeout(function(){var id;self._lastTickTime=AnimationFrame.now();self._timeoutId=null;++self._tickCounter;for(id in self._callbacks){if(self._callbacks[id]){if(AnimationFrame.hasNative&&self.options.useNative){nativeRequestAnimationFrame(self._callbacks[id])}else{self._callbacks[id](AnimationFrame.perfNow())}delete self._callbacks[id]}}},delay)}this._callbacks[this._tickCounter]=callback;return this._tickCounter};AnimationFrame.prototype.cancel=function(id){if(AnimationFrame.hasNative&&this.options.useNative)nativeCancelAnimationFrame(id);delete this._callbacks[id]};if(typeof exports=="object"&&typeof module=="object"){module.exports=AnimationFrame}else if(typeof define=="function"&&define.amd){define(function(){return AnimationFrame})}else{window.AnimationFrame=AnimationFrame}})(window);

View File

@ -1,5 +1,11 @@
window.onload = function () { window.onload = function () {
// Array to hold all active spells being drawn
var spells = [];
// Save this query here now that the page is loaded
var canvas = document.getElementById("magic");
/* Get the height and width of full document. To avoid browser /* Get the height and width of full document. To avoid browser
* incompatibility issues, choose the maximum of all height/width values. * incompatibility issues, choose the maximum of all height/width values.
* *
@ -37,7 +43,10 @@ window.onload = function () {
If the red, green, or blue value of the color is at 0 or 255 and the 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 gradient for that value is not zero, then the gradient for that value
will change signs. */ 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. */
function nextColor(color, gradient) { function nextColor(color, gradient) {
var values = ["r", "g", "b"]; var values = ["r", "g", "b"];
@ -66,7 +75,7 @@ window.onload = function () {
"b": Math.floor(Math.random() * (255 + 1))}; "b": Math.floor(Math.random() * (255 + 1))};
} }
function drawSplineSegment(branch, t, context) { function drawSplineSegment(branch, context) {
var ax = (-branch.points[0].x + 3*branch.points[1].x - 3*branch.points[2].x + branch.points[3].x) / 6; 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 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 bx = (branch.points[0].x - 2*branch.points[1].x + branch.points[2].x) / 2;
@ -76,16 +85,18 @@ window.onload = function () {
var dx = (branch.points[0].x + 4*branch.points[1].x + branch.points[2].x) / 6; 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; var dy = (branch.points[0].y + 4*branch.points[1].y + branch.points[2].y) / 6;
context.moveTo( context.moveTo(
ax*Math.pow(t, 3) + bx*Math.pow(t, 2) + cx*t + dx, ax*Math.pow(branch.t, 3) + bx*Math.pow(branch.t, 2) + cx*branch.t + dx,
ay*Math.pow(t, 3) + by*Math.pow(t, 2) + cy*t + dy ay*Math.pow(branch.t, 3) + by*Math.pow(branch.t, 2) + cy*branch.t + dy
); );
context.lineTo( context.lineTo(
ax*Math.pow(t+0.2, 3) + bx*Math.pow(t+0.2, 2) + cx*(t+0.2) + dx, ax*Math.pow(branch.t+0.2, 3) + bx*Math.pow(branch.t+0.2, 2) + cx*(branch.t+0.2) + dx,
ay*Math.pow(t+0.2, 3) + by*Math.pow(t+0.2, 2) + cy*(t+0.2) + dy ay*Math.pow(branch.t+0.2, 3) + by*Math.pow(branch.t+0.2, 2) + cy*(branch.t+0.2) + dy
); );
branch.t += 0.2;
} }
function splitBranch(branch, new_branches) { function splitBranch(branch) {
var newBranches = [];
// Replace with 2 new branches // Replace with 2 new branches
for (var k = 0; k < 2; k++) { for (var k = 0; k < 2; k++) {
@ -100,7 +111,7 @@ window.onload = function () {
var y2 = branch.points[3].y - Math.cos(Math.PI * angle / 180) * length; var y2 = branch.points[3].y - Math.cos(Math.PI * angle / 180) * length;
// Add to new branch array // Add to new branch array
new_branches.push({ newBranches.push({
points:new Array( points:new Array(
branch.points[1], branch.points[1],
branch.points[2], branch.points[2],
@ -108,124 +119,54 @@ window.onload = function () {
{x:x2, y:y2} {x:x2, y:y2}
), ),
angle:angle, angle:angle,
t:0,
}); });
} }
return newBranches;
} }
function createNewBranches(branches) { function cast(x, y, angle) {
// Create array to store next iteration of branchces // Find random position if not defined
var new_branches = []; if (x === undefined) {
x = Math.floor(Math.random() * (canvas.width + 1));
// Iterate over each branch }
for (var j in branches) { if (y === undefined) {
splitBranch(branches[j], new_branches); y = Math.floor(Math.random() * (canvas.height + 1));
}
if (angle === undefined) {
angle = Math.random() * 360;
} }
return new_branches; var color = pickRandomColor();
var colorString = "rgb(" + color.r + "," + color.g + "," + color.b + ")";
// Create new spell (branch)
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() * (600 - 50 + 1)) + 50,
});
} }
/* Most of this funtion is provided by Maissan Inc. in their tutorial: /* Most of this funtion is provided by Maissan Inc. in their tutorial:
http://www.maissan.net/articles/simulating-vines */ http://www.maissan.net/articles/simulating-vines */
function drawTendrils(context, x, y, iterations, sort, prune, prune_to) { function drawSpells(context, sort, prune, prune_to) {
var AnimationFrame = window.AnimationFrame;
// Set stroke colour AnimationFrame.shim();
var animationFrame = new AnimationFrame(30),
timeCounter = 0,
waitTime = 80,
lastCast = 0,
color,
gradient;
context.lineWidth = 0.5; context.lineWidth = 0.5;
var color = pickRandomColor();
context.strokeStyle = "rgb(" + color.r + "," + color.g + "," + color.b + ")";
// Create initial branch
var branches = [];
branches.push({
points:new Array({x:x, y:y}, {x:x, y:y}, {x:x, y:y}, {x:x, y:y}),
angle:(Math.random() * 360),
});
// Start drawing splines at t=0
var t = 0;
// Drawing interval
var interval = setInterval(function() {
var gradient = pickGradient();
// Set stroke color
var newColor = nextColor(color, gradient);
color = newColor.color;
gradient = newColor.gradient;
context.strokeStyle = "rgb(" + color.r + "," + color.g + "," + color.b + ")";
context.beginPath();
// Draw branches
for (var i in branches) {
drawSplineSegment(branches[i], t, context);
}
context.stroke();
context.closePath();
// Advance t
t += 0.2;
// When finished drawing splines, create a new set of branches
if (t >= 1) {
var new_branches = createNewBranches(branches);
// If over 10 branches, prune the branches
if (prune) {
if (sort) {
while (new_branches.length > 20) new_branches.pop();
} else {
while (new_branches.length > prune_to) {
new_branches.splice(Math.floor(Math.random() * new_branches.length), 1);
}
}
}
// Replace old branch array with new
branches = new_branches;
// Restart drawing splines at t=0
t = 0;
}
// Count interations
iterations--;
if (iterations < 0) clearInterval(interval);
}, 32);
// Return interval
return interval;
}
function canvasClickHandler(event) {
var x = event.pageX;
var y = event.pageY;
var duration = Math.floor(Math.random() * (600 - 50 + 1)) + 50;
var canvas = document.getElementById("magic");
var context = canvas.getContext("2d");
var interval = drawTendrils(context, x, y, duration, false, true, 15);
}
function draw() {
var interval_time = 2000;
var metaInterval;
function cast() {
console.log(interval_time);
clearInterval(metaInterval);
// Go slower once started
if (interval_time === 2000) {
interval_time = 4000;
} else if (interval_time > 20000) {
return; // stop drawing
} else {
interval_time = interval_time * 1.1;
}
function animate(time) {
// resize canvas if document size changed // resize canvas if document size changed
dimensions = getDocumentDimensions(); dimensions = getDocumentDimensions();
if ((dimensions.height !== canvas.height) || if ((dimensions.height !== canvas.height) ||
@ -234,18 +175,89 @@ window.onload = function () {
canvas.width = dimensions.width; canvas.width = dimensions.width;
} }
// Find random position // if enough time has passed, cast another spell to draw
var x = Math.floor(Math.random() * (canvas.width + 1)); if ((timeCounter - lastCast) >= waitTime) {
var y = Math.floor(Math.random() * (canvas.height + 1)); if (waitTime > 500) {
var duration = Math.floor(Math.random() * (600 - 50 + 1)) + 50; return; // stop drawing
var interval = drawTendrils(context, x, y, duration, false, true, 15); } else if (waitTime === 80){
waitTime = 125;
metaInterval = setInterval(cast, interval_time); } else {
waitTime = waitTime * 1.1;
} }
console.log("cast: " + waitTime);
lastCast = timeCounter;
if (waitTime === 125) {
cast(5, 5, 270); // start position
} else {
cast(undefined, undefined, undefined); // random spell position
}
}
// Draw branches
for (var i in spells) {
context.beginPath();
context.strokeStyle = spells[i].color;
if (spells[i].duration > 0) {
for (var j in spells[i].branches) {
drawSplineSegment(spells[i].branches[j], context);
// When finished drawing splines, create a new set of branches
if (spells[i].branches[j].t >= 1) {
var newBranches = splitBranch(spells[i].branches[j]);
// Replace old branch with two new branches
spells[i].branches.splice(j, 1);
spells[i].branches = spells[i].branches.concat(newBranches);
}
}
// If over 10 branches, prune the branches
if (prune) {
if (sort) {
while (spells[i].branches.length > prune_to) {
spells[i].branches.pop();
}
} else {
while (spells[i].branches.length > prune_to) {
spells[i].branches.splice(
Math.floor(Math.random() * spells[i].branches.length),
1);
}
}
}
context.stroke();
context.closePath();
spells[i].duration--;
} else {
spells.splice(i, 1); // spell is done now
}
}
timeCounter++;
frameId = animationFrame.request(animate);
}
// Drawing interval
var frameId = animationFrame.request(animate);
return frameId;
}
function canvasClickHandler(event) {
var x = event.pageX;
var y = event.pageY;
cast(x, y, undefined);
}
function draw() {
var interval_time = 2000;
// Initialize canvas // Initialize canvas
console.log("draw");
var canvas = document.getElementById("magic");
var dimensions = getDocumentDimensions(); var dimensions = getDocumentDimensions();
console.log(dimensions.height); console.log(dimensions.height);
console.log(dimensions.width); console.log(dimensions.width);
@ -259,7 +271,7 @@ window.onload = function () {
var context = canvas.getContext("2d"); var context = canvas.getContext("2d");
// Cast magic spells // Cast magic spells
metaInterval = setInterval(cast, interval_time); var frameId = drawSpells(context, false, true, 30);
} }
} }