Skip to content

Instantly share code, notes, and snippets.

@leoken
Created September 11, 2012 22:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leoken/3702738 to your computer and use it in GitHub Desktop.
Save leoken/3702738 to your computer and use it in GitHub Desktop.
Draw Worm
<canvas id='canvas'></canvas>
<!--
Click to clear the canvas
-->
// Draw Worm
// Ported from flash demo - http://wonderfl.net/c/9os2
//
function DrawWorm(){
var canvas;
var context;
var width;
var height;
var mouse = {x: window.innerWidth/2, y: window.innerHeight};
this.mouse = mouse;
var interval;
var vms = [];
var MAX_NUM = 100;
var N = 80;
var px = window.innerWidth/2;
var py = window.innerHeight;
this.initialize = function(){
canvas = document.getElementById("canvas");
context = canvas.getContext('2d');
width = window.innerWidth;
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
canvas.addEventListener('touchmove', TouchMove, false);
canvas.addEventListener('mousemove', MouseMove, false);
canvas.addEventListener('click', MouseDown, false);
//Set interval - Bad! - I know!
var interval = setInterval(Draw, 20);
}
var Draw = function(){
var len = vms.length;
var i;
//fadeScreen();
for (i = 0; i < len; i++){
var o = vms[i];
if (o.count < N){
DrawWorm(o);
o.count++;
//This looks a tad hacky - modifying the loop from within :S
} else {
len--;
vms.splice(i, 1);
i--;
}
}
Check();
}
//Takes a worm (obj) param
var DrawWorm = function (obj){
if (Math.random() > 0.9){
obj.tmt.rotate(-obj.r * 2);
obj.r *= -1;
}
//Prepend is just concat -- right?
obj.vmt.prependMatrix(obj.tmt);
var cc1x = -obj.w * obj.vmt.c + obj.vmt.tx;
var cc1y = -obj.w * obj.vmt.d + obj.vmt.ty;
var pp1x = (obj.c1x + cc1x) / 2;
var pp1y = (obj.c1y + cc1y) / 2;
var cc2x = obj.w * obj.vmt.c + obj.vmt.tx;
var cc2y = obj.w * obj.vmt.d + obj.vmt.ty;
var pp2x = (obj.c2x + cc2x) / 2;
var pp2y = (obj.c2y + cc2y) / 2;
context.fillStyle = '#000000';
context.strokeStyle = '#000000';
context.beginPath();
context.moveTo(obj.p1x , obj.p1y);
context.quadraticCurveTo(obj.c1x, obj.c1y, pp1x, pp1y);
context.lineTo(pp2x, pp2y);
context.quadraticCurveTo(obj.c2x, obj.c2y, obj.p2x, obj.p2y);
//context.stroke();
context.closePath();
context.fill();
obj.c1x = cc1x;
obj.c1y = cc1y;
obj.p1x = pp1x;
obj.p1y = pp1y;
obj.c2x = cc2x;
obj.c2y = cc2y;
obj.p2x = pp2x;
obj.p2y = pp2y;
}
var Check = function(){
var x0 = mouse.x;
var y0 = mouse.y;
var vx = x0 - px;
var vy = y0 - py;
var len = Math.min(Magnitude(vx, vy), 50);
if (len < 10){
return;
}
var matrix = new Matrix2D();
matrix.rotate((Math.atan2(vy, vx)));
matrix.translate(x0, y0);
createWorm(matrix, len);
context.beginPath();
context.strokeStyle = '#000000';
context.moveTo(px, py);
context.lineTo(x0, y0);
context.stroke();
context.closePath();
px = x0;
py = y0;
//More logic here for afterwards?
}
var createWorm = function(mtx, len){
var angle = Math.random() * (Math.PI/6 - Math.PI/64) + Math.PI/64;
if(Math.random() > 0.5){
angle *= -1;
}
var tmt = new Matrix2D();
tmt.scale(0.95, 0.95);
tmt.rotate(angle);
tmt.translate(len, 0);
var w = 0.5;
var obj = new Worm();
obj.c1x = (-w * mtx.c + mtx.tx);
obj.p1x = (-w * mtx.c + mtx.tx);
obj.c1y = (-w * mtx.d + mtx.ty);
obj.p1y = (-w * mtx.d + mtx.ty);
obj.c2x = (w * mtx.c + mtx.tx);
obj.p2x = (w * mtx.c + mtx.tx);
obj.c2y = (w * mtx.d + mtx.ty);
obj.p2y = (w * mtx.d + mtx.ty);
obj.vmt = mtx;
obj.tmt = tmt;
obj.r = angle;
obj.w = len/20;
obj.count = 0;
vms.push(obj);
if (vms.length > MAX_NUM){
vms.shift();
}
}
//Not sure why they do this kinda thing in flash.
var Worm = function(){
this.c1x = null;
this.c1y = null;
this.c2x = null;
this.c2y = null;
this.p1x = null;
this.p1y = null;
this.p2x = null;
this.p2y = null;
this.w = null;
this.r = null;
this.count = null;
this.vmt = null;
this.tmt = null;
}
var fadeScreen = function(){
context.fillStyle = 'rgba(255, 255, 255, 0.02)';
context.beginPath();
context.rect(0, 0, width, height);
context.closePath();
context.fill();
}
//Clear the screen,
var MouseDown = function(e) {
e.preventDefault();
canvas.width = canvas.width;
vms = [];
}
var MouseMove = function(e) {
mouse.x = e.layerX - canvas.offsetLeft;
mouse.y = e.layerY - canvas.offsetTop;
}
var TouchMove = function(e) {
e.preventDefault();
mouse.x = e.targetTouches[0].pageX - canvas.offsetLeft;
mouse.y = e.targetTouches[0].pageY - canvas.offsetTop;
}
//Returns Magnitude
var Magnitude = function(x, y){
return Math.sqrt((x * x) + (y * y));
}
}
//Hacked up matrix, borrowed from easel.js -- thanks grant!
//
// -- If you are forking this, then you will want to play with the code above this line.
// -- Unless you are totally nuts!
//
(function(window) {
var Matrix2D = function(a, b, c, d, tx, ty) {
this.initialize(a, b, c, d, tx, ty);
}
var p = Matrix2D.prototype;
// static public properties:
Matrix2D.identity = null; // set at bottom of class definition.
Matrix2D.DEG_TO_RAD = Math.PI/180;
p.a = 1;
p.b = 0;
p.c = 0;
p.d = 1;
p.tx = 0;
p.ty = 0;
p.alpha = 1;
p.shadow = null;
p.compositeOperation = null;
p.initialize = function(a, b, c, d, tx, ty) {
if (a != null) { this.a = a; }
this.b = b || 0;
this.c = c || 0;
if (d != null) { this.d = d; }
this.tx = tx || 0;
this.ty = ty || 0;
}
p.prepend = function(a, b, c, d, tx, ty) {
var n11 = a * this.a + b * this.c;
var n12 = a * this.b + b * this.d;
var n21 = c * this.a + d * this.c;
var n22 = c * this.b + d * this.d;
var n31 = tx * this.a + ty * this.c + this.tx;
var n32 = tx * this.b + ty * this.d + this.ty;
this.a = n11;
this.b = n12;
this.c = n21;
this.d = n22;
this.tx = n31;
this.ty = n32;
}
p.append = function(a, b, c, d, tx, ty) {
var a1 = this.a;
var b1 = this.b;
var c1 = this.c;
var d1 = this.d;
this.a = a*a1+b*c1;
this.b = a*b1+b*d1;
this.c = c*a1+d*c1;
this.d = c*b1+d*d1;
this.tx = tx*a1+ty*c1+this.tx;
this.ty = tx*b1+ty*d1+this.ty;
}
p.prependMatrix = function(matrix) {
this.prepend(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);
this.prependProperties(matrix.alpha, matrix.shadow, matrix.compositeOperation);
}
p.appendMatrix = function(matrix) {
this.append(matrix.a, matrix.b, matrix.c, matrix.d, matrix.tx, matrix.ty);
this.appendProperties(matrix.alpha, matrix.shadow, matrix.compositeOperation);
}
p.prependTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) {
if (rotation%360) {
var r = rotation*Matrix2D.DEG_TO_RAD;
var cos = Math.cos(r);
var sin = Math.sin(r);
} else {
cos = 1;
sin = 0;
}
if (regX || regY) {
// append the registration offset:
this.tx -= regX; this.ty -= regY;
}
if (skewX || skewY) {
// TODO: can this be combined into a single prepend operation?
skewX *= Matrix2D.DEG_TO_RAD;
skewY *= Matrix2D.DEG_TO_RAD;
this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0);
this.prepend(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y);
} else {
this.prepend(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y);
}
}
p.appendTransform = function(x, y, scaleX, scaleY, rotation, skewX, skewY, regX, regY) {
if (rotation%360 == 0 && scaleX == 1 && scaleY == 1 && skewX == 0 && skewY == 0) {
this.tx += x-regX;
this.ty += y-regY;
return;
}
if (rotation%360) {
var r = rotation*Matrix2D.DEG_TO_RAD;
var cos = Math.cos(r);
var sin = Math.sin(r);
} else {
cos = 1;
sin = 0;
}
if (skewX || skewY) {
// TODO: can this be combined into a single append?
skewX *= Matrix2D.DEG_TO_RAD;
skewY *= Matrix2D.DEG_TO_RAD;
this.append(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), x, y);
this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, 0, 0);
} else {
this.append(cos*scaleX, sin*scaleX, -sin*scaleY, cos*scaleY, x, y);
}
if (regX || regY) {
// prepend the registration offset:
this.tx -= regX*this.a+regY*this.c;
this.ty -= regX*this.b+regY*this.d;
}
}
p.rotate = function(angle) {
var sin = Math.sin(angle);
var cos = Math.cos(angle);
var n11 = cos * this.a + sin * this.c;
var n12 = cos * this.b + sin * this.d;
var n21 = -sin * this.a + cos * this.c;
var n22 = -sin * this.b + cos * this.d;
this.a = n11;
this.b = n12;
this.c = n21;
this.d = n22;
}
p.skew = function(skewX, skewY) {
skewX = skewX*Matrix2D.DEG_TO_RAD;
skewY = skewY*Matrix2D.DEG_TO_RAD;
this.append(Math.cos(skewY), Math.sin(skewY), -Math.sin(skewX), Math.cos(skewX), 0, 0);
}
p.scale = function(x, y) {
this.a *= x;
this.d *= y;
this.tx *= x;
this.ty *= y;
}
p.translate = function(x, y) {
this.tx += x;
this.ty += y;
}
p.identity = function() {
this.alpha = this.a = this.d = 1;
this.b = this.c = this.tx = this.ty = 0;
this.shadow = this.compositeOperation = null;
}
p.invert = function() {
var a1 = this.a;
var b1 = this.b;
var c1 = this.c;
var d1 = this.d;
var tx1 = this.tx;
var n = a1*d1-b1*c1;
this.a = d1/n;
this.b = -b1/n;
this.c = -c1/n;
this.d = a1/n;
this.tx = (c1*this.ty-d1*tx1)/n;
this.ty = -(a1*this.ty-b1*tx1)/n;
}
p.isIdentity = function() {
return this.tx == 0 && this.ty == 0 && this.a == 1 && this.b == 0 && this.c == 0 && this.d == 1;
}
p.decompose = function(target) {
// TODO: it would be nice to be able to solve for whether the matrix can be decomposed into only scale/rotation
// even when scale is negative
if (target == null) { target = {}; }
target.x = this.tx;
target.y = this.ty;
target.scaleX = Math.sqrt(this.a * this.a + this.b * this.b);
target.scaleY = Math.sqrt(this.c * this.c + this.d * this.d);
var skewX = Math.atan2(-this.c, this.d);
var skewY = Math.atan2(this.b, this.a);
if (skewX == skewY) {
target.rotation = skewY/Matrix2D.DEG_TO_RAD;
if (this.a < 0 && this.d >= 0) {
target.rotation += (target.rotation <= 0) ? 180 : -180;
}
target.skewX = target.skewY = 0;
} else {
target.skewX = skewX/Matrix2D.DEG_TO_RAD;
target.skewY = skewY/Matrix2D.DEG_TO_RAD;
}
return target;
}
p.reinitialize = function(a,b,c,d,tx,ty,alpha,shadow,compositeOperation) {
this.initialize(a,b,c,d,tx,ty);
this.alpha = alpha || 1;
this.shadow = shadow;
this.compositeOperation = compositeOperation;
return this;
}
p.appendProperties = function(alpha, shadow, compositeOperation) {
this.alpha *= alpha;
this.shadow = shadow || this.shadow;
this.compositeOperation = compositeOperation || this.compositeOperation;
}
p.prependProperties = function(alpha, shadow, compositeOperation) {
this.alpha *= alpha;
this.shadow = this.shadow || shadow;
this.compositeOperation = this.compositeOperation || compositeOperation;
}
p.clone = function() {
var mtx = new Matrix2D(this.a, this.b, this.c, this.d, this.tx, this.ty);
mtx.shadow = this.shadow;
mtx.alpha = this.alpha;
mtx.compositeOperation = this.compositeOperation;
return mtx;
}
p.toString = function() {
return "[Matrix2D (a="+this.a+" b="+this.b+" c="+this.c+" d="+this.d+" tx="+this.tx+" ty="+this.ty+")]";
}
// this has to be populated after the class is defined:
Matrix2D.identity = new Matrix2D(1, 0, 0, 1, 0, 0);
window.Matrix2D = Matrix2D;
}(window));
var app = new DrawWorm();
app.initialize();
count = 0;
function demoApp() {
count++;
if (count % 2 == 0){
app.mouse.y -= 40
} else {
app.mouse.y += 40;
}
if (count > 30) {
window.clearInterval( interval );
}
}
var interval = setInterval( demoApp, 20 );
body {
margin:0px;
overflow: hidden;
background: rgb(255,255,255);
background: -moz-radial-gradient(center, ellipse cover, rgba(255,255,255,1) 0%, rgba(171,209,234,1) 100%);
background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(255,255,255,1)), color-stop(100%,rgba(171,209,234,1)));
background: -webkit-radial-gradient(center, ellipse cover, rgba(255,255,255,1) 0%,rgba(171,209,234,1) 100%);
background: -o-radial-gradient(center, ellipse cover, rgba(255,255,255,1) 0%,rgba(171,209,234,1) 100%);
background: -ms-radial-gradient(center, ellipse cover, rgba(255,255,255,1) 0%,rgba(171,209,234,1) 100%);
background: radial-gradient(ellipse at center, rgba(255,255,255,1) 0%,rgba(171,209,234,1) 100%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#abd1ea',GradientType=1 );
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment