topical media & game development
student-canvas-article.js / js
function Vector(x, y)
{
this.x = x;
this.y = y;
this.equal = function(v)
{
return this.x == v.getX() && this.y == v.getY();
}
this.getX = function()
{
return this.x;
}
this.getY = function()
{
return this.y;
}
this.setX = function(x)
{
this.x = x;
}
this.setY = function(y)
{
this.y = y;
}
this.addX = function(x)
{
this.x += x;
}
this.addY = function(y)
{
this.y += y;
}
this.set = function(v)
{
this.x = v.getX();
this.y = v.getY();
}
this.add = function(v)
{
this.x += v.getX();
this.y += v.getY();
}
this.sub = function(v)
{
this.x -= v.getX();
this.y -= v.getY();
}
this.dotProd = function(v)
{
return this.x * v.getX() + this.y * v.getY();
}
this.dist = function(v)
{
return Math.sqrt((this.x - v.getX()) * (this.x - v.getX()) + (this.y - v.getY()) * (this.y - v.getY()));
}
this.length = function()
{
return Math.sqrt(this.x * this.x + this.y * this.y);
}
this.scale = function(scaleFactor)
{
this.x *= scaleFactor;
this.y *= scaleFactor;
}
this.normalize = function()
{
var l = this.length();
this.x /= l;
this.y /= l;
}
this.toString = function()
{
return " X: " + this.x + " Y: " + this.y;
}
}
function Environment(x, y, w, h)
{
this.left = x;
this.right = x + w;
this.top = y;
this.buttom = y + h;
this.r = new Vector(0.0, 0.0);
this.collision = function(curPos, prevPos)
{
var collide = false;
var i;
if(curPos.getX() < this.left)
{
curPos.setX(this.left);
collide = true;
}
else if(curPos.getX() > this.right)
{
curPos.setX(this.right);
collide = true;
}
if(curPos.getY() < this.top)
{
curPos.setY(this.top);
collide = true;
}
else if(curPos.getY() > this.buttom)
{
curPos.setY(this.buttom);
collide = true;
}
return collide;
}
this.draw = function(ctx, scaleFactor)
{
ctx.lineWidth = 2;
ctx.strokeStyle = '#cccccc';
ctx.beginPath();
ctx.moveTo(this.left * scaleFactor, this.top * scaleFactor);
ctx.lineTo(this.right * scaleFactor, this.top * scaleFactor);
ctx.lineTo(this.right * scaleFactor, this.buttom * scaleFactor);
ctx.lineTo(this.left * scaleFactor, this.buttom * scaleFactor);
ctx.closePath();
ctx.stroke();
}
}
function PointMass(cx, cy, mass)
{
this.cur = new Vector(cx, cy);
this.prev = new Vector(cx, cy);
this.mass = mass;
this.force = new Vector(0.0, 0.0);
this.result = new Vector(0.0, 0.0);
this.friction = 0.01;
this.getXPos = function()
{
return this.cur.getX();
}
this.getYPos = function()
{
return this.cur.getY();
}
this.getPos = function()
{
return this.cur;
}
this.getXPrevPos = function()
{
return this.prev.getX();
}
this.getYPrevPos = function()
{
return this.prev.getY();
}
this.getPrevPos = function()
{
return this.prev;
}
this.addXPos = function(dx)
{
this.cur.addX(dx);
}
this.addYPos = function(dy)
{
this.cur.addY(dy);
}
this.setForce = function(force)
{
this.force.set(force);
}
this.addForce = function(force)
{
this.force.add(force);
}
this.getMass = function()
{
return this.mass;
}
this.setMass = function(mass)
{
this.mass = mass;
}
this.setPos = function(x, y)
{
this.cur.setX(x);
this.cur.setY(y);
}
this.setPrevPos = function(x, y)
{
this.prev.setX(x);
this.prev.setY(y);
}
this.move = function(dt)
{
var t, a, c, dtdt;
dtdt = dt * dt;
a = this.force.getX() / this.mass;
c = this.cur.getX();
t = (2.0 - this.friction) * c - (1.0 - this.friction) * this.prev.getX() + a * dtdt;
this.prev.setX(c);
this.cur.setX(t);
a = this.force.getY() / this.mass;
c = this.cur.getY();
t = (2.0 - this.friction) * c - (1.0 - this.friction) * this.prev.getY() + a * dtdt;
this.prev.setY(c);
this.cur.setY(t);
}
this.setFriction = function(friction)
{
this.friction = friction;
}
this.getVelocity = function()
{
var cXpX, cYpY;
cXpX = this.cur.getX() - this.prev.getX();
cYpY = this.cur.getY() - this.prev.getY();
return cXpX * cXpX + cYpY * cYpY;
}
this.draw = function(ctx, scaleFactor)
{
ctx.lineWidth = 5;
ctx.fillStyle = '#000000';
ctx.strokeStyle = '#000000';
ctx.beginPath();
ctx.arc(this.cur.getX() * scaleFactor,
this.cur.getY() * scaleFactor,
6.0, 0.0, Math.PI * 2.0, true);
ctx.fill();
}
}
function Joint(pointMassA, pointMassB, shortConst, longConst)
{
this.pointMassA = pointMassA;
this.pointMassB = pointMassB;
this.delta = new Vector(0.0, 0.0);
this.pointMassAPos = pointMassA.getPos();
this.pointMassBPos = pointMassB.getPos();
this.delta.set(this.pointMassBPos);
this.delta.sub(this.pointMassAPos);
this.shortConst = this.delta.length() * shortConst;
this.longConst = this.delta.length() * longConst;
this.scSquared = this.shortConst * this.shortConst;
this.lcSquared = this.longConst * this.longConst;
this.middle = new Vector();
this.t = new Vector();
this.k = new Vector();
this.setDist = function(shortConst, longConst)
{
this.shortConst = shortConst;
this.longConst = longConst;
this.scSquared = this.shortConst * this.shortConst;
this.lcSquared = this.longConst * this.longConst;
}
this.scale = function(scaleFactor)
{
this.shortConst = this.shortConst * scaleFactor;
this.longConst = this.longConst * scaleFactor;
this.scSquared = this.shortConst * this.shortConst;
this.lcSquared = this.longConst * this.longConst;
}
this.sc = function()
{
this.delta.set(this.pointMassBPos);
this.delta.sub(this.pointMassAPos);
var dp = this.delta.dotProd(this.delta);
if(this.shortConst != 0.0 && dp < this.scSquared)
{
var scaleFactor;
scaleFactor = this.scSquared / (dp + this.scSquared) - 0.5;
this.delta.scale(scaleFactor);
this.pointMassAPos.sub(this.delta);
this.pointMassBPos.add(this.delta);
}
else if(this.longConst != 0.0 && dp > this.lcSquared)
{
var scaleFactor;
scaleFactor = this.lcSquared / (dp + this.lcSquared) - 0.5;
this.delta.scale(scaleFactor);
this.pointMassAPos.sub(this.delta);
this.pointMassBPos.add(this.delta);
}
}
this.draw = function(ctx, scaleFactor)
{
this.middle.set(this.pointMassA.getPos());
this.t.set(this.pointMassB.getPos());
this.t.sub(this.middle);
this.t.scale(0.5);
this.middle.add(this.t);
this.t.set(this.middle);
this.t.sub(this.pointMassA.getPos());
ctx.lineWidth = 2;
// long const bar
this.t.normalize();
this.t.scale(this.longConst * 0.5);
var t1 = this.middle.getX() + this.t.getX();
var t2 = this.middle.getY() + this.t.getY();
var t3 = this.middle.getX() - this.t.getX();
var t4 = this.middle.getY() - this.t.getY();
ctx.strokeStyle = '#aaaaaa';
ctx.beginPath();
ctx.moveTo(t1 * scaleFactor, t2 * scaleFactor);
ctx.lineTo(t3 * scaleFactor, t4 * scaleFactor);
ctx.stroke();
this.k.set(this.t);
this.k.normalize();
this.k.scale(0.03);
var x = -this.k.getY();
var y = this.k.getX();
ctx.strokeStyle = '#0000ff';
ctx.beginPath();
ctx.moveTo((t1 + x) * scaleFactor, (t2 + y) * scaleFactor);
ctx.lineTo((t1 - x) * scaleFactor, (t2 - y) * scaleFactor);
ctx.stroke();
ctx.beginPath();
ctx.moveTo((t3 + x) * scaleFactor, (t4 + y) * scaleFactor);
ctx.lineTo((t3 - x) * scaleFactor, (t4 - y) * scaleFactor);
ctx.stroke();
// short const bar
this.t.normalize();
this.t.scale(this.shortConst * 0.5);
t1 = this.middle.getX() + this.t.getX();
t2 = this.middle.getY() + this.t.getY();
t3 = this.middle.getX() - this.t.getX();
t4 = this.middle.getY() - this.t.getY();
ctx.strokeStyle = '#ff0000';
ctx.beginPath();
ctx.moveTo((t1 + x) * scaleFactor, (t2 + y) * scaleFactor);
ctx.lineTo((t1 - x) * scaleFactor, (t2 - y) * scaleFactor);
ctx.stroke();
ctx.beginPath();
ctx.moveTo((t3 + x) * scaleFactor, (t4 + y) * scaleFactor);
ctx.lineTo((t3 - x) * scaleFactor, (t4 - y) * scaleFactor);
ctx.stroke();
}
}
var env;
var width = 600.0;
var height = 400.0;
var scaleFactor = 200.0;
var blobColl;
var gravity;
var stopped;
var savedMouseCoords = null;
var selectOffset = null;
function update()
{
var dt = 0.05;
if(savedMouseCoords != null && selectOffset != null)
{
blobColl.selectedBlobMoveTo(savedMouseCoords.x - selectOffset.x,
savedMouseCoords.y - selectOffset.y);
}
blobColl.move(dt);
blobColl.sc(env);
blobColl.setForce(gravity);
}
function draw()
{
var canvas = document.getElementById('blob');
if(canvas.getContext == null)
{
return;
}
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, width, height);
env.draw(ctx, scaleFactor);
blobColl.draw(ctx, scaleFactor);
}
function timeout()
{
draw();
update();
if(stopped == false)
{
setTimeout('timeout()', 30);
}
}
function init()
{
var canvas = document.getElementById('blob');
if(canvas.getContext == null)
{
alert("You need Firefox version 1.5 or higher for this to work, sorry.");
return;
}
document.onkeydown = function(event)
{
var keyCode;
if(event == null)
{
keyCode = window.event.keyCode;
}
else
{
keyCode = event.keyCode;
}
switch(keyCode)
{
// left
case 37:
blobColl.addForce(new Vector(-50.0, 0.0));
break;
// up
case 38:
blobColl.addForce(new Vector(0.0, -50.0));
break;
// right
case 39:
blobColl.addForce(new Vector(50.0, 0.0));
break;
// down
case 40:
blobColl.addForce(new Vector(0.0, 50.0));
break;
// join 'j'
case 74:
blobColl.join();
break;
// split 'h'
case 72:
blobColl.split();
break;
// toggle gravity 'g'
case 71:
toggleGravity();
break;
default:
break;
}
}
function getMouseCoords(event)
{
if(event == null)
{
event = window.event;
}
if(event == null)
{
return null;
}
if(event.pageX || event.pageY){
return {x:event.pageX / scaleFactor, y:event.pageY / scaleFactor};
}
return null;
}
document.onmousedown = function(event)
{
var mouseCoords;
if(stopped == true)
{
return;
}
mouseCoords = getMouseCoords(event);
if(mouseCoords == null)
{
return;
}
selectOffset = blobColl.selectBlob(mouseCoords.x, mouseCoords.y);
}
document.onmouseup = function(event)
{
blobColl.unselectBlob();
savedMouseCoords = null;
selectOffset = null;
}
document.onmousemove = function(event)
{
var mouseCoords;
if(stopped == true)
{
return;
}
if(selectOffset == null)
{
return;
}
mouseCoords = getMouseCoords(event);
if(mouseCoords == null)
{
return;
}
blobColl.selectedBlobMoveTo(mouseCoords.x - selectOffset.x, mouseCoords.y - selectOffset.y);
savedMouseCoords = mouseCoords;
}
env = new Environment(0.2, 0.2, 2.6, 1.6);
blobColl = new BlobCollective(1.0, 1.0, 1, 200);
gravity = new Vector(0.0, 10.0);
stopped = false;
timeout();
}
function stop()
{
stopped = true;
}
function start()
{
stopped = false;
timeout();
}
function toggleGravity()
{
if(gravity.getY() > 0.0)
{
gravity.setY(0.0);
}
else
{
gravity.setY(10.0);
}
}
(C) Æliens
04/09/2009
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.