Doodle3D-Slicer/library/cal.js
2015-05-29 13:51:18 +02:00

2330 lines
63 KiB
JavaScript

//canvas cheat sheet
//http://cheatsheetworld.com/programming/html5-canvas-cheat-sheet/
//TODO
//Global Composite Operation
//Linear Gradient
//Radial Gradient
//Bezier Curve
//Circle
//Touch support
"use strict";
var CAL = {
name: "Canvas Abstraction Layer",
version: "1.0",
author: "Casper Lamboo",
contact: "casperlamboo@gmail.com"
};
CAL.Math = {
clamb: function (value, min, max) {
return (value > min) ? ((value < max) ? value : max) : min;
},
randomInt: function (min, max) {
return Math.round(CAL.Math.random(min, max));
},
random: function (min, max) {
min = min || 0;
max = max === undefined ? 1 : max;
return Math.random()*(max - min) + min;
},
sign: function (value) {
return (value > 0) ? 1 : ((value < 0) ? -1 : 0);
},
lineCollision: function (v1, v2, v3, v4) {
//bron: http://mathworld.wolfram.com/Line-LineIntersection.html
var intersection = new CAL.Vector(
((v1.x*v2.y-v1.y*v2.x)*(v3.x-v4.x)-(v1.x-v2.x)*(v3.x*v4.y-v3.y*v4.x)) / ((v1.x-v2.x)*(v3.y-v4.y)-(v1.y-v2.y)*(v3.x-v4.x)),
((v1.x*v2.y-v1.y*v2.x)*(v3.y-v4.y)-(v1.y-v2.y)*(v3.x*v4.y-v3.y*v4.x)) / ((v1.x-v2.x)*(v3.y-v4.y)-(v1.y-v2.y)*(v3.x-v4.x))
);
var line1 = v1.subtract(v2).length();
var line2 = v3.subtract(v4).length();
var a = line1 >= v1.subtract(intersection).length();
var b = line1 >= v2.subtract(intersection).length();
var c = line2 >= v3.subtract(intersection).length();
var d = line2 >= v4.subtract(intersection).length();
return (a && b && c && d) ? intersection : false;
}
};
CAL.Easings = {
bounceEaseOut: function (dt, b, c, d) {
if ((dt /= d) < (1 / 2.75)) {
return c * (7.5625 * dt * dt) + b;
}
else if (dt < (2 / 2.75)) {
return c * (7.5625 * (dt -= (1.5 / 2.75)) * dt + 0.75) + b;
}
else if (dt < (2.5 / 2.75)) {
return c * (7.5625 * (dt -= (2.25 / 2.75)) * dt + 0.9375) + b;
}
else {
return c * (7.5625 * (dt -= (2.625 / 2.75)) * dt + 0.984375) + b;
}
},
easeIn: function (dt, b, c, d) {
return c * (dt /= d) * dt + b;
},
easeOut: function (dt, b, c, d) {
return -c * (dt /= d) * (dt - 2) + b;
},
easeInOut: function (dt, b, c, d) {
if ((dt /= d / 2) < 1) {
return c / 2 * dt * dt + b;
}
return -c / 2 * ((--dt) * (dt - 2) - 1) + b;
},
strongEaseIn: function (dt, b, c, d) {
return c * (dt /= d) * dt * dt * dt * dt + b;
},
strongEaseOut: function (dt, b, c, d) {
return c * (( dt = dt / d - 1) * dt * dt * dt * dt + 1) + b;
},
strongEaseInOut: function (dt, b, c, d) {
if ((dt /= d / 2) < 1) {
return c / 2 * dt * dt * dt * dt * dt + b;
}
return c / 2 * ((dt -= 2) * dt * dt * dt * dt + 2) + b;
},
linear: function (dt, b, c, d) {
return c * dt / d + b;
}
};
//this doesn't work, everything is an instance of Object in JavaScript
/*Object.prototype.clone = function () {
var object = {};
for (var i in this) {
var element = this[i];
object[i] = element.clone ? element.clone() : element;
}
return object;
};
Object.prototype.foreach = function (callback) {
for (var i in this) {
var element = this[i];
callback(element, i);
}
};*/
Array.prototype.foreach = function (callback, scope) {
for (var i = 0; i < this.length; i ++) {
var element = this[i];
if ((scope !== undefined) ? callback.call(scope, element, i) : callback(element, i)) {
break;
}
}
};
Array.prototype.foreachReverse = function (callback, scope) {
for (var i = this.length-1; i >= 0; i --) {
var element = this[i];
if ((scope !== undefined) ? callback.call(scope, element, i) : callback(element, i)) {
break;
}
}
};
Array.prototype.max = function () {
var max = -Infinity;
this.foreach(function (element) {
if (element > max) {
max = element;
}
});
return max;
};
Array.prototype.min = function () {
var min = Infinity;
this.foreach(function (element) {
if (element < min) {
min = element;
}
});
return min;
};
Array.prototype.clone = function () {
var array = [];
this.foreach(function (element) {
array.push((element.clone !== undefined) ? element.clone() : element);
});
return array;
};
Array.prototype.remove = function () {
for (var i = 0; i < arguments.length; i ++) {
var element = arguments[i];
var index = this.indexOf(element);
if (index !== -1) {
this.splice(index, 1);
}
}
};
var requestAnimFrame = (function () {
return requestAnimationFrame || webkitRequestAnimationFrame || mozRequestAnimationFrame || function (callback) {
setTimeout(callback, 1000/60);
};
})();
CAL.Vector = function (x, y) {
this.x = x || 0;
this.y = y || 0;
};
CAL.Vector.prototype.add = function (vector) {
var x = this.x + vector.x;
var y = this.y + vector.y;
return new CAL.Vector(x, y);
};
CAL.Vector.prototype.subtract = function (vector) {
var x = this.x - vector.x;
var y = this.y - vector.y;
return new CAL.Vector(x, y);
};
CAL.Vector.prototype.scale = function (scalar) {
var x = this.x * scalar;
var y = this.y * scalar;
return new CAL.Vector(x, y);
};
CAL.Vector.prototype.rotate = function (angle) {
var cos = Math.cos(angle);
var sin = Math.sin(angle);
var x = cos*this.x - sin*this.y;
var y = sin*this.x + cos*this.y;
return new CAL.Vector(x, y);
};
CAL.Vector.prototype.multiply = function (vector) {
var x = this.x * vector.x;
var y = this.y * vector.y;
return new CAL.Vector(x, y);
};
CAL.Vector.prototype.length = function () {
return Math.sqrt(this.x*this.x + this.y*this.y);
};
CAL.Vector.prototype.normal = function () {
return new CAL.Vector(this.y, -this.x);
};
CAL.Vector.prototype.normalize = function () {
var length = this.length();
var x = this.x/length;
var y = this.y/length;
return new CAL.Vector(x, y);
};
CAL.Vector.prototype.angle = function () {
return Math.atan2(this.y, this.x);
};
CAL.Vector.prototype.dot = function (vector) {
return this.x * vector.x + this.y * vector.y;
};
CAL.Vector.prototype.cross = function (vector) {
return this.x * vector.y - this.y * vector.x;
};
CAL.Vector.prototype.round = function () {
var x = Math.round(this.x);
var y = Math.round(this.y);
return new CAL.Vector(x, y);
};
CAL.Vector.prototype.applyMatrix = function (matrix) {
var m = matrix.matrix;
var x = m[0]*this.x + m[1]*this.y + m[2];
var y = m[3]*this.x + m[4]*this.y + m[5];
return new CAL.Vector(x, y);
};
CAL.Vector.prototype.clone = function () {
return new CAL.Vector(this.x, this.y);
};
CAL.Vector.prototype.draw = function (context, x, y) {
var end = new CAL.Vector(this.x + x, this.y + y);
var arrowOrigin = new CAL.Vector(x, y).add(this.subtract(this.normalize().scale(10)));
var left = this.normal().normalize().scale(10).add(arrowOrigin);
var right = this.normal().normalize().scale(-10).add(arrowOrigin);
context.beginPath();
context.moveTo(x, y);
context.lineTo(end.x, end.y);
context.moveTo(left.x, left.y);
context.lineTo(end.x, end.y);
context.lineTo(right.x, right.y);
context.stroke();
};
CAL.Matrix = function (options) {
options = options || {};
if (options.matrix !== undefined && options.sx !== undefined && options.sy !== undefined && options.rotation !== undefined && options.x !== undefined && options.y !== undefined) {
this.matrix = options.matrix;
this._sx = options.sx;
this._sy = options.sy;
this._rotation = options.rotation;
this._x = options.x;
this._y = options.y;
}
else if (options instanceof Array || options.matrix) {
this.matrix = options.matrix || options;
this._sx = CAL.Math.sign(this.matrix[0])*Math.sqrt(Math.pow(this.matrix[0], 2) + Math.pow(this.matrix[1], 2));
this._sy = CAL.Math.sign(this.matrix[4])*Math.sqrt(Math.pow(this.matrix[3], 2) + Math.pow(this.matrix[4], 2));
this._rotation = Math.atan2(-this.matrix[1], this.matrix[0]);
this._x = this.matrix[2];
this._y = this.matrix[5];
//source: http://math.stackexchange.com/questions/13150/extracting-rotation-scale-values-from-2d-transformation-matrix
//BUG doesn't convert right when both sx and sy aren't 1
}
else {
this._sx = options.sx !== undefined ? options.sx : 1;
this._sy = options.sy !== undefined ? options.sy : 1;
this._x = options.x || 0;
this._y = options.y || 0;
if (options.rotation === undefined) {
this._rotation = 0;
this.matrix = [
this._sx, 0, this._x,
0, this._sy, this._y
];
}
else {
this._rotation = options.rotation;
this.updateMatrix();
}
}
};
CAL.Matrix.prototype = {
get sx () {
return this._sx;
},
set sx (sx) {
this._sx = sx;
this.updateMatrix();
},
get sy () {
return this._sy;
},
set sy (sy) {
this._sy = sy;
this.updateMatrix();
},
get rotation () {
return this._rotation;
},
set rotation (rotation) {
this._rotation = rotation;
this.updateMatrix();
},
get x () {
return this._x;
},
set x (x) {
this._x = this.matrix[2] = x;
},
get y () {
return this._y;
},
set y (y) {
this._y = this.matrix[5] = y;
}
};
CAL.Matrix.prototype.updateMatrix = function () {
this.matrix = [
this._sx * Math.cos(this._rotation), this._sy * -Math.sin(this._rotation), this._x,
this._sx * Math.sin(this._rotation), this._sy * Math.cos(this._rotation), this._y
];
};
CAL.Matrix.prototype.multiplyMatrix = function (matrix) {
var a = this.matrix;
var b = matrix.matrix;
var translation = new CAL.Vector(b[2], b[5]).applyMatrix(this);
return new CAL.Matrix([
a[0]*b[0] + a[3]*b[1], a[1]*b[0] + a[4]*b[1], translation.x,
a[0]*b[3] + a[3]*b[4], a[1]*b[3] + a[4]*b[4], translation.y,
]);
};
CAL.Matrix.prototype.inverse = function () {
var m = this.matrix;
return new CAL.Matrix([
m[4], -m[1], -m[2],
-m[3], m[0], -m[5]
]);
};
CAL.Matrix.prototype.translate = function (x, y) {
this.x += x;
this.y += y;
this.matrix[2] = this.x;
this.matrix[5] = this.y;
return this;
};
CAL.Matrix.prototype.setMatrix = function (matrix) {
this.matrix = matrix.matrix.clone();
this._x = matrix._x;
this._y = matrix._y;
this._sx = matrix._sx;
this._sy = matrix._sy;
this._rotation = matrix._rotation;
};
CAL.Matrix.prototype.setContext = function (context) {
var m = this.matrix;
context.transform(m[0], m[3], m[1], m[4], m[2], m[5]);
};
CAL.Matrix.prototype.rotateAroundAbsolute = function (angle, center) {
if (angle !== 0) {
var center = center
.subtract(new CAL.Vector(this.x, this.y))
.rotate(-this.rotation)
.multiply(new CAL.Vector(1/this.sx, 1/this.sy));
this.rotateAroundRelative(angle, center);
}
};
CAL.Matrix.prototype.rotateAroundRelative = function (angle, center) {
if (angle !== 0) {
var before = center.applyMatrix(this);
this.rotation += angle;
var after = center.applyMatrix(this);
var offset = before.subtract(after);
this._x += offset.x;
this._y += offset.y;
this.updateMatrix();
}
};
CAL.Matrix.prototype.clone = function () {
return new CAL.Matrix({
matrix: this.matrix.clone(),
sx: this._sx,
sy: this._sy,
rotation: this._rotation,
x: this._x,
y: this._y
});
};
CAL.Draw = function (centerX, centerY, numberWidth, numberHeight, options) {
CAL.Matrix.call(this, options);
this.visible = options.visible !== undefined ? options.visible : true;
this.active = options.active || false;
this.depth = options.depth || 0;
this.alpha = (typeof options.alpha === "number") ? options.alpha : 1;
this.centerX = centerX || 0;
this.centerY = centerY || 0;
this.index = 0;
this.numberWidth = numberWidth || 1;
this.numberHeight = numberHeight || 1;
this.length = this.numberWidth*this.numberHeight;
};
CAL.Draw.prototype = Object.create(CAL.Matrix.prototype);
CAL.Draw.prototype.draw = function (context, matrix) {
context.save();
(matrix || this).setContext(context);
context.globalAlpha = this.alpha;
this.drawSimple(context, this.index, 0, 0);
context.restore();
};
CAL.Draw.prototype.drawSimple = function (context, number, x, y) {
var sx = (number % this.numberWidth)*this.width;
var sy = Math.floor(number/this.numberWidth)*this.height;
context.drawImage(this.image, sx, sy, this.width, this.height, x-this.centerX, y-this.centerY, this.width, this.height);
};
CAL.Draw.prototype.drawAlpha = function (context, number, x, y, alpha) {
context.globalAlpha = alpha;
this.drawSimple(context, number, x, y);
context.globalAlpha = 1;
};
CAL.Draw.prototype.drawAngle = function (context, number, x, y, angle) {
context.save();
context.translate(x, y);
context.rotate(angle);
this.drawSimple(context, number, 0, 0);
context.restore();
};
CAL.Draw.prototype.drawScale = function (context, number, x, y, width, height) {
var sx = (number % this.numberWidth)*this.width;
var sy = Math.floor(number/this.numberWidth)*this.height;
context.drawImage(this.image, sx, sy, this.width, this.height, x-this.centerX, y-this.centerY, width, height);
};
CAL.Draw.prototype.drawContain = function (context, number, x, y, width, height) {
if (width/height > this.width/this.height) {
x = x + (width-height/this.height*this.width)/2;
width = height/this.height*this.width;
this.drawScale(context, number, x, y, width, height);
}
else {
y = y + (height-width/this.width*this.height)/2;
height = width/this.width*this.height;
this.drawScale(context, number, x, y, width, height);
}
};
CAL.Surface = function (options) {
options = options || {};
CAL.Draw.call(this, options.centerX, options.centerY, options.numberWidth, options.numberHeight, options);
this.clearColor = options.clearColor || false;
this.setCanvas(options.canvas || document.createElement("canvas"));
this.setSize(options.width, options.height);
};
CAL.Surface.prototype = Object.create(CAL.Draw.prototype);
CAL.Surface.prototype.setSize = function (width, height) {
this.image.width = width || this.image.width;
this.image.height = height || this.image.height;
this.width = this.image.width/this.numberWidth;
this.height = this.image.height/this.numberHeight;
};
CAL.Surface.prototype.setCanvas = function (canvas) {
this.image = canvas;
this.context = canvas.getContext("2d");
};
CAL.Surface.prototype.clear = function () {
if (this.clearColor) {
this.clearColor.setColor(this.context);
this.context.fillRect(0, 0, this.image.width, this.image.height);
}
else {
this.context.clearRect(0, 0, this.image.width, this.image.height);
}
};
CAL.Surface.prototype.getImageData = function (x, y, width, height) {
var x = x || 0;
var y = y || 0;
var width = width || this.image.width;
var height = height || this.image.height;
return this.context.getImageData(x, y, width, height);
};
CAL.Surface.prototype.getDataURL = function () {
return this.image.toDataURL();
};
CAL.Surface.prototype.blur = (function () {
//source: http://www.quasimondo.com/StackBlurForCanvas/StackBlurDemo.html
//author: Mario Klingemann
var mul_table = [512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,289,287,285,282,280,278,275,273,271,269,267,265,263,261,259];
var shg_table = [9,11,12,13,13,14,14,15,15,15,15,16,16,16,16,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,19,19,19,19,19,19,19,19,19,19,19,19,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24 ];
return function (radius, x, y, width, height) {
x = x || 0;
y = y || 0;
width = this.image.width || 0;
height = this.image.height || 0;
var imageData = this.getImageData(x, y, width, height);
var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum,
r_out_sum, g_out_sum, b_out_sum, a_out_sum,
r_in_sum, g_in_sum, b_in_sum, a_in_sum,
pr, pg, pb, pa, rbs;
var div = radius + radius + 1;
var w4 = width << 2;
var widthMinus1 = width - 1;
var heightMinus1 = height - 1;
var radiusPlus1 = radius + 1;
var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;
var pixels = imageData.data;
var stackStart = {r: 0, g: 0, b: 0, next: null};
var stack = stackStart;
for (i = 1; i < div; i ++) {
stack = stack.next = {r: 0, g: 0, b: 0, next: null};
if (i == radiusPlus1) {
var stackEnd = stack;
}
}
stack.next = stackStart;
var stackIn = null;
var stackOut = null;
yw = yi = 0;
var mul_sum = mul_table[radius];
var shg_sum = shg_table[radius];
for (y = 0; y < height; y ++) {
r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0;
r_out_sum = radiusPlus1 * (pr = pixels[yi]);
g_out_sum = radiusPlus1 * (pg = pixels[yi+1]);
b_out_sum = radiusPlus1 * (pb = pixels[yi+2]);
a_out_sum = radiusPlus1 * (pa = pixels[yi+3]);
r_sum += sumFactor * pr;
g_sum += sumFactor * pg;
b_sum += sumFactor * pb;
a_sum += sumFactor * pa;
stack = stackStart;
for (i = 0; i < radiusPlus1; i ++) {
stack.r = pr;
stack.g = pg;
stack.b = pb;
stack.a = pa;
stack = stack.next;
}
for (i = 1; i < radiusPlus1; i ++) {
p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
r_sum += (stack.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i);
g_sum += (stack.g = (pg = pixels[p+1])) * rbs;
b_sum += (stack.b = (pb = pixels[p+2])) * rbs;
a_sum += (stack.a = (pa = pixels[p+3])) * rbs;
r_in_sum += pr;
g_in_sum += pg;
b_in_sum += pb;
a_in_sum += pa;
stack = stack.next;
}
stackIn = stackStart;
stackOut = stackEnd;
for (x = 0; x < width; x ++) {
pixels[yi+3] = pa = (a_sum * mul_sum) >> shg_sum;
if (pa === 0) {
pixels[yi] = pixels[yi+1] = pixels[yi+2] = 0;
}
else {
pa = 255 / pa;
pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa;
pixels[yi+1] = ((g_sum * mul_sum) >> shg_sum) * pa;
pixels[yi+2] = ((b_sum * mul_sum) >> shg_sum) * pa;
}
r_sum -= r_out_sum;
g_sum -= g_out_sum;
b_sum -= b_out_sum;
a_sum -= a_out_sum;
r_out_sum -= stackIn.r;
g_out_sum -= stackIn.g;
b_out_sum -= stackIn.b;
a_out_sum -= stackIn.a;
p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2;
r_in_sum += (stackIn.r = pixels[p]);
g_in_sum += (stackIn.g = pixels[p+1]);
b_in_sum += (stackIn.b = pixels[p+2]);
a_in_sum += (stackIn.a = pixels[p+3]);
r_sum += r_in_sum;
g_sum += g_in_sum;
b_sum += b_in_sum;
a_sum += a_in_sum;
stackIn = stackIn.next;
r_out_sum += (pr = stackOut.r);
g_out_sum += (pg = stackOut.g);
b_out_sum += (pb = stackOut.b);
a_out_sum += (pa = stackOut.a);
r_in_sum -= pr;
g_in_sum -= pg;
b_in_sum -= pb;
a_in_sum -= pa;
stackOut = stackOut.next;
yi += 4;
}
yw += width;
}
for (x = 0; x < width; x ++) {
g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0;
yi = x << 2;
r_out_sum = radiusPlus1 * (pr = pixels[yi]);
g_out_sum = radiusPlus1 * (pg = pixels[yi+1]);
b_out_sum = radiusPlus1 * (pb = pixels[yi+2]);
a_out_sum = radiusPlus1 * (pa = pixels[yi+3]);
r_sum += sumFactor * pr;
g_sum += sumFactor * pg;
b_sum += sumFactor * pb;
a_sum += sumFactor * pa;
stack = stackStart;
for (i = 0; i < radiusPlus1; i ++) {
stack.r = pr;
stack.g = pg;
stack.b = pb;
stack.a = pa;
stack = stack.next;
}
yp = width;
for (i = 1; i <= radius; i ++) {
yi = (yp + x) << 2;
r_sum += (stack.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i);
g_sum += (stack.g = (pg = pixels[yi+1])) * rbs;
b_sum += (stack.b = (pb = pixels[yi+2])) * rbs;
a_sum += (stack.a = (pa = pixels[yi+3])) * rbs;
r_in_sum += pr;
g_in_sum += pg;
b_in_sum += pb;
a_in_sum += pa;
stack = stack.next;
if (i < heightMinus1) {
yp += width;
}
}
yi = x;
stackIn = stackStart;
stackOut = stackEnd;
for (y = 0; y < height; y ++) {
p = yi << 2;
pixels[p+3] = pa = (a_sum * mul_sum) >> shg_sum;
if (pa > 0) {
pa = 255 / pa;
pixels[p] = ((r_sum * mul_sum) >> shg_sum) * pa;
pixels[p+1] = ((g_sum * mul_sum) >> shg_sum) * pa;
pixels[p+2] = ((b_sum * mul_sum) >> shg_sum) * pa;
}
else {
pixels[p] = pixels[p+1] = pixels[p+2] = 0;
}
r_sum -= r_out_sum;
g_sum -= g_out_sum;
b_sum -= b_out_sum;
a_sum -= a_out_sum;
r_out_sum -= stackIn.r;
g_out_sum -= stackIn.g;
b_out_sum -= stackIn.b;
a_out_sum -= stackIn.a;
p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2;
r_sum += (r_in_sum += (stackIn.r = pixels[p]));
g_sum += (g_in_sum += (stackIn.g = pixels[p+1]));
b_sum += (b_in_sum += (stackIn.b = pixels[p+2]));
a_sum += (a_in_sum += (stackIn.a = pixels[p+3]));
stackIn = stackIn.next;
r_out_sum += (pr = stackOut.r);
g_out_sum += (pg = stackOut.g);
b_out_sum += (pb = stackOut.b);
a_out_sum += (pa = stackOut.a);
r_in_sum -= pr;
g_in_sum -= pg;
b_in_sum -= pb;
a_in_sum -= pa;
stackOut = stackOut.next;
yi += width;
}
}
return imageData;
};
})();
CAL.Draw.prototype.drawBlur = (function () {
var surface = new CAL.Surface();
return function (context, number, x, y, radius) {
if (radius > 0) {
surface.setSize(this.width + 2*radius, this.height + 2*radius);
this.drawSimple(surface.context, number, this.centerX+radius, this.centerY+radius);
var imageData = surface.blur(radius);
context.putImageData(imageData, x-this.centerX - radius, y-this.centerY - radius);
/*surface.setSize(this.width, this.height);
this.drawSimple(surface.context, number, this.centerX, this.centerY);
var imageData = surface.blur(radius);
context.putImageData(imageData, x-this.centerX, y-this.centerY);*/
}
else {
this.drawSimple(context, number, x, y);
}
};
})();
CAL.Group = function (options) {
options = options || {};
CAL.Surface.call(this, options);
this.active = true;
this.visible = true;
this.objects = [];
this.useCanvas = options.useCanvas || false;
this.clearCanvas = true;
this.drawCanvas = true;
this.mouse = {
x: null,
y: null,
startX: null,
startY: null,
deltaX: null,
deltaY: null,
down: false,
moved: false
};
};
CAL.Group.prototype = Object.create(CAL.Surface.prototype);
CAL.Group.prototype.updateEvents = function () {
var scope = this;
this.image.onmousedown = function (event) {
if (scope.useCanvas) {
scope.mouse.x = scope.mouse.startX = Math.round(scope.image.width / scope.image.clientWidth * event.offsetX);
scope.mouse.y = scope.mouse.startY = Math.round(scope.image.height / scope.image.clientHeight * event.offsetY);
scope.mouse.deltaX = 0;
scope.mouse.deltaY = 0;
scope.mouse.down = true;
scope.mouse.moved = false;
scope.mouseDown(scope.mouse, this);
}
};
this.image.onmouseup = function (event) {
if (scope.useCanvas) {
scope.mouse.x = Math.round(scope.image.width / scope.image.clientWidth * event.offsetX);
scope.mouse.y = Math.round(scope.image.height / scope.image.clientHeight * event.offsetY);
scope.mouse.down = false;
scope.mouseUp(scope.mouse, this);
scope.mouse.startX = null;
scope.mouse.startY = null;
scope.mouse.deltaX = null;
scope.mouse.deltaY = null;
scope.mouse.moved = false;
}
};
this.image.onmousemove = function (event) {
if (scope.useCanvas) {
scope.mouse.x = Math.round(scope.image.width / scope.image.clientWidth * event.offsetX);
scope.mouse.y = Math.round(scope.image.height / scope.image.clientHeight * event.offsetY);
if (scope.mouse.down) {
scope.mouse.moved = true;
scope.mouse.deltaX = scope.mouse.x - scope.mouse.startX;
scope.mouse.deltaY = scope.mouse.y - scope.mouse.startY;
}
}
};
//this.image.ontouchstart = function (event) {scope.touchStart(event);};
//this.image.ontouchmove = function (event) {scope.touchMove(event);};
//this.image.ontouchend = function (event) {scope.touchEnd(event);};
};
CAL.Group.prototype.setCanvas = function (canvas) {
/*this.image.onmousedown = null;
this.image.onmouseup = null;
this.image.onmousemove = null;
this.image.ontouchstart = null;
this.image.ontouchmove = null;
this.image.ontouchend = null;*/
this.image = canvas;
this.context = canvas.getContext("2d");
this.drawCanvas = true;
this.updateEvents();
};
CAL.Group.prototype.add = function () {
for (var i = 0; i < arguments.length; i ++) {
var object = arguments[i];
if (this.objects.indexOf(object) === -1) {
this.objects.push(object);
if (object.init) {
object.init(this);
}
}
}
this.sort();
this.drawCanvas = true;
};
CAL.Group.prototype.remove = function () {
for (var i = 0; i < arguments.length; i ++) {
var object = arguments[i];
this.objects.remove(object);
if (object.active && object.remove !== undefined) {
object.remove(this);
}
}
this.drawCanvas = true;
};
CAL.Group.prototype.sort = function () {
this.objects.sort(function (a, b) {
return (a.depth || 0) - (b.depth || 0);
});
};
CAL.Group.prototype.keyDown = function (keyCode) {
for (var i = this.objects.length-1; i >= 0; i --) {
var object = this.objects[i];
if (object.active && object.keyDown !== undefined) {
if (object.keyDown(keyCode, this)) {
break;
}
}
}
};
CAL.Group.prototype.keyUp = function (keyCode) {
for (var i = this.objects.length-1; i >= 0; i --) {
var object = this.objects[i];
if (object.active && object.keyUp !== undefined) {
if (object.keyUp(keyCode, this)) {
break;
}
}
}
};
CAL.Group.prototype.mouseDown = function (mouse) {
for (var i = this.objects.length-1; i >= 0; i --) {
var object = this.objects[i];
if (object.useCanvas !== true && object.active && object.mouseDown !== undefined) {
if (object.mouseDown(mouse, this)) {
break;
}
}
}
};
CAL.Group.prototype.mouseUp = function (mouse) {
for (var i = this.objects.length-1; i >= 0; i --) {
var object = this.objects[i];
if (object.useCanvas !== true && object.active && object.mouseUp !== undefined) {
if (object.mouseUp(mouse, this)) {
break;
}
}
}
};
CAL.Group.prototype.step = function (deltaTime) {
for (var i = 0; i < this.objects.length; i ++) {
var object = this.objects[i];
if (object.active && object.step !== undefined) {
object.step(deltaTime, this);
}
}
if (this.clearCanvas && this.useCanvas) {
this.clear();
}
if (this.drawCanvas && this.useCanvas) {
this.draw();
}
this.clearCanvas = false;
this.drawCanvas = false;
};
CAL.Group.prototype.draw = function (context, matrix) {
context = this.useCanvas ? this.context : context;
matrix = this.useCanvas ? this : matrix;
for (var i = 0; i < this.objects.length; i ++) {
var object = this.objects[i];
if (object.useCanvas !== true && object.visible && object.draw !== undefined) {
if (object instanceof CAL.Matrix) {
object.draw(context, matrix.multiplyMatrix(object));
}
else {
object.draw(context, matrix);
}
}
}
};
CAL.Scene = function () {
CAL.Group.call(this, {useCanvas: true});
this.lastTime = new Date().getTime();
this.keysDown = [];
this.focus = true;
var scope = this;
window.onkeydown = function (event) {
if (!scope.keysDown[event.keyCode]) {
scope.keysDown[event.keyCode] = true;
scope.keyDown(event.keyCode);
}
};
window.onkeyup = function (event) {
scope.keysDown[event.keyCode] = false;
scope.keyUp(event.keyCode);
};
window.onblur = function (event) {
this.focus = false;
};
window.onfocus = function (event) {
scope.lastTime = new Date().getTime();
this.focus = true;
};
};
CAL.Scene.prototype = Object.create(CAL.Group.prototype);
CAL.Scene.prototype.cycle = function () {
if (this.focus) {
var currentTime = new Date().getTime();
var deltaTime = currentTime-this.lastTime;
this.lastTime = currentTime;
this.step(deltaTime);
}
};
CAL.Scene = new CAL.Scene();
CAL.Image = function (source, centerX, centerY, numberWidth, numberHeight, options) {
options = options || {};
CAL.Draw.call(this, centerX, centerY, numberWidth, numberHeight, options);
this.image = new Image();
this.source = source;
};
CAL.Image.prototype = Object.create(CAL.Draw.prototype);
CAL.Image.prototype.load = function (callback) {
var scope = this;
this.image.onload = function () {
scope.loaded = true;
scope.width = scope.image.width/scope.numberWidth;
scope.height = scope.image.height/scope.numberHeight;
if (callback !== undefined) {
callback();
}
};
this.image.src = this.source;
};
CAL.ImageLoader = function () {
this.images = [];
for (var i = 0; i < arguments.length; i ++) {
var image = arguments[i];
this.images.push(image);
}
};
CAL.ImageLoader.prototype.add = function () {
for (var i = 0; i < arguments.length; i ++) {
var image = arguments[i];
if (this.images.indexOf(image) === -1) {
this.images.push(image);
}
}
};
CAL.ImageLoader.prototype.remove = function () {
for (var i = 0; i < arguments.length; i ++) {
var image = arguments[i];
this.images.remove(image);
}
};
CAL.ImageLoader.prototype.load = function (callback) {
var imagesToLoad = this.images.length;
for (var i = 0; i < this.images.length; i ++) {
var image = this.images[i];
image.load(function () {
imagesToLoad --;
if (imagesToLoad === 0 && callback !== undefined) {
callback();
}
}, this);
};
};
CAL.Color = function () {
if (typeof arguments[0] === "number" && typeof arguments[1] === "number" && typeof arguments[2] === "number") {
this.r = arguments[0];
this.g = arguments[1];
this.b = arguments[2];
this.a = typeof arguments[3] === "number" ? arguments[3] : 1;
}
else if (typeof arguments[0] === "number") {
var hex = Math.floor(arguments[0]);
this.r = hex >> 16 & 255;
this.g = hex >> 8 & 255;
this.b = hex & 255;
this.a = 1;
}
else {
this.r = 0;
this.g = 0;
this.b = 0;
this.a = 1;
}
};
CAL.Color.prototype.setStroke = function (context) {
context.strokeStyle = "rgba("+this.r+", "+this.g+", "+this.b+", "+this.a+")";
};
CAL.Color.prototype.setFill = function (context) {
context.fillStyle = "rgba("+this.r+", "+this.g+", "+this.b+", "+this.a+")";
};
CAL.Color.prototype.setColor = function (context) {
this.setStroke(context);
this.setFill(context);
};
CAL.Tween = function (object, attributes, duration, options) {
options = options || {};
this.visible = false;
this.active = true;
this.depth = -10000;
this.object = object;
this.attributes = attributes;
this.timer = 0;
this.duration = duration;
this.easing = options.easing || CAL.Easings.linear;
this.callback = options.callback;
this.begin = {};
for (var i in attributes) {
this.begin[i] = this.object[i];
}
this.change = {};
for (var i in attributes) {
this.change[i] = attributes[i] - this.begin[i];
}
this.drawCanvas = options.drawCanvas !== undefined ? options.drawCanvas : true;
this.clearCanvas = options.clearCanvas !== undefined ? options.clearCanvas : false;
};
CAL.Tween.prototype.start = function () {
this.t = 0;
this.active = true;
};
CAL.Tween.prototype.stop = function () {
this.t = 0;
this.active = false;
};
CAL.Tween.prototype.pause = function () {
this.active = false;
};
CAL.Tween.prototype.resume = function () {
this.active = true;
};
CAL.Tween.prototype.step = function (deltaTime, group) {
this.timer += deltaTime;
if (this.timer < this.duration) {
for (var i in this.attributes) {
var dt = this.timer;
var d = this.duration;
var b = this.begin[i];
var c = this.change[i];
this.object[i] = this.easing(dt, b, c, d);
}
}
else {
for (var i in this.attributes) {
this.object[i] = this.attributes[i];
}
if (this.callback !== undefined) {
this.callback();
}
group.remove(this);
}
if (this.clearCanvas) {
group.clearCanvas = true;
}
if (this.drawCanvas) {
group.drawCanvas = true;
}
};
CAL.TimeLine = function (options) {
options = options || {};
this.visible = false;
this.active = true;
this.depth = -10000;
this.moments = [];
this.autoRemove = (options.autoRemove !== undefined) ? options.autoremove : true;
this.loop = (options.loop !== undefined) ? options.loop : false;
this.t = 0;
CAL.Scene.add(this);
};
CAL.TimeLine.prototype = {
addMoment: function (time, callback) {
this.moments.push({
time: time,
callback: callback
});
},
removeMoment: function (remove) {
for (var i = 0; i < this.moments.length; i ++) {
var moment = this.moments[i];
if (moment === remove || moment.time === remove || moment.callback === remove) {
this.moments.remove(moment);
}
}
},
start: function () {
this.t = 0;
this.active = true;
},
stop: function () {
this.t = 0;
this.active = false;
},
pause: function () {
this.active = false;
},
resume: function () {
this.active = true;
},
step: function (dt) {
var newTime = this.t + dt;
var remove = true;
for (var i = 0; i < this.moments.length; i ++) {
var moment = this.moments[i];
if (moment.time >= this.t) {
if (moment.time < newTime) {
moment.callback();
}
else {
remove = false;
}
}
}
if (remove && this.loop) {
this.t = 0;
}
else if (remove && this.autoRemove) {
CAL.Scene.remove(this);
}
this.t = newTime;
}
};
CAL.Shape = function (options) {
options = options || {};
CAL.Matrix.call(this, options);
this.visible = options.visible !== undefined ? options.visible : true;
this.active = false;
this.depth = options.depth || 0;
this.lines = [];
this.closePath = options.closePath !== undefined ? options.closePath : true;
this.lineColor = options.lineColor !== undefined ? options.lineColor : new CAL.Color();
this.shapeColor = options.shapeColor !== undefined ? options.shapeColor : new CAL.Color();
this.lineWidth = options.lineWidth || 1;
this.lineJoin = options.lineJoin || "miter";
this.lineCap = options.lineCap || "square";
this.points = options.points || [];
};
CAL.Shape.prototype = Object.create(CAL.Matrix.prototype);
CAL.Shape.prototype.addPoint = function () {
for (var i = 0; i < arguments.length; i ++) {
var point = arguments[i];
this.points.push(point);
}
//this.update();
};
CAL.Shape.prototype.hit = function (x, y) {
for (var i = 0; i < this.points.length; i ++) {
if (new CAL.Vector(x, y).subtract(this.points[i].applyMatrix(this)).dot(this.getNormal(i)) > 0) {
return false;
}
}
return true;
};
CAL.Shape.prototype.setContext = function (context, matrix) {
var matrix = matrix || this;
context.beginPath();
for (var i = 0; i < this.points.length; i ++) {
var point = this.points[i].applyMatrix(matrix);
context.lineTo(point.x, point.y);
}
if (this.closePath) {
context.closePath();
}
};
CAL.Shape.prototype.getBoundingBox = function () {
var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;
for (var i = 0; i < this.points.length; i ++) {
var point = this.points[i];
minX = point.x < minX ? point.x : minX;
minY = point.y < minY ? point.y : minY;
maxX = point.x > maxX ? point.x : maxX;
maxY = point.y > maxY ? point.y : maxY;
};
return {x: minX, y: minY, width: maxX-minX, height: maxY-minY};
};
CAL.Shape.prototype.getNormal = function (i) {
var pointA = this.points[(i+1)%this.points.length].applyMatrix(this);
var pointB = this.points[i].applyMatrix(this);
return pointA.subtract(pointB).normal().normalize();
};
CAL.Shape.prototype.clip = function (context, matrix) {
this.setContext(context, matrix);
context.clip();
};
CAL.Shape.prototype.fill = function (context, matrix) {
this.setContext(context, matrix);
this.shapeColor.setFill(context);
context.fill();
};
CAL.Shape.prototype.stroke = function (context, matrix) {
this.setContext(context, matrix);
context.lineColor = this.lineColor;
context.lineWidth = this.lineWidth;
context.lineJoin = this.lineJoin;
context.lineCap = this.lineCap;
this.lineColor.setStroke(context);
context.stroke();
};
CAL.Shape.prototype.draw = function (context, matrix) {
this.setContext(context, matrix);
if (this.shapeColor) {
this.shapeColor.setFill(context);
context.fill();
}
if (this.lineColor) {
context.lineColor = this.lineColor;
context.lineWidth = this.lineWidth;
context.lineJoin = this.lineJoin;
context.lineCap = this.lineCap;
this.lineColor.setStroke(context);
context.stroke();
}
};
CAL.BezierPoint = function (position, controlPointA, controlPointB) {
this.position = position || new CAL.Vector(0, 0);
this.controlPointA = controlPointA || new CAL.Vector(0, 0);
this.controlPointB = controlPointB || new CAL.Vector(0, 0);
}
CAL.BezierPoint.prototype.applyMatrix = function (matrix) {
var position = this.position.applyMatrix(matrix);
var controlPointA = this.controlPointA.applyMatrix(matrix);
var controlPointB = this.controlPointB.applyMatrix(matrix);
return new CAL.BezierPoint(position, controlPointA, controlPointB);
};
CAL.BezierPoint.prototype.draw = function (context) {
var leftHandle = this.controlPointA.add(this.position);
var rightHandle = this.controlPointB.add(this.position);
context.strokeStyle = "#09F";
context.beginPath();
context.moveTo(leftHandle.x, leftHandle.y);
context.lineTo(this.position.x, this.position.y);
context.lineTo(rightHandle.x, rightHandle.y);
context.stroke();
context.beginPath();
context.arc(this.position.x, this.position.y, 10, 0, Math.PI*2, true);
context.stroke();
context.beginPath();
context.arc(leftHandle.x, leftHandle.y, 5, 0, Math.PI*2, true);
context.stroke();
context.beginPath();
context.arc(rightHandle.x, rightHandle.y, 5, 0, Math.PI*2, true);
context.stroke();
};
/*CAL.Shape = function (options) {
options = options || {};
CAL.Matrix.call(this, options);
this.visible = options.visible !== undefined ? options.visible : true;
this.active = false;
this.depth = options.depth || 0;
this.closePath = options.closePath !== undefined ? options.closePath : true;
this.lineColor = options.lineColor !== undefined ? options.lineColor : new CAL.Color();
this.shapeColor = options.shapeColor !== undefined ? options.shapeColor : new CAL.Color();
this.lineWidth = options.lineWidth || 1;
this.lineJoin = options.lineJoin || "miter";
this.lineCap = options.lineCap || "square";
this.points = options.points || [];
this.precision = options.precision || 3;
this.lines = [];
this.length = 0;
this.closed = false;
}
CAL.Shape.prototype.init = function (group) {
for (var i = 0; i < this.points.length; i ++) {
var point = this.points[i];
if (point instanceof CAL.Vector) {
point = new CAL.BezierPoint(point);
}
this.points[i] = point;
}
this.update();
};
CAL.Shape.prototype.addPoint = function () {
for (var i = 0; i < arguments.length; i ++) {
var point = arguments[i];
if (point instanceof CAL.Vector) {
point = new CAL.BezierPoint(point);
}
if (point instanceof CAL.BezierPoint) {
this.points.push(point);
}
}
this.update();
};
CAL.Shape.prototype.collisionBox = function (vec1, vec2) {
for (var i = 0; i < this.lines.length; i ++) {
var point = this.lines[i];
if (point.x > vec1.x && point.y > vec1.y && point.x < vec2.x && point.y < vec2.y) {
return true;
}
}
return false;
};
CAL.Shape.prototype.getNormal = function (i) {
var pointA = this.lines[(i+1)%this.lines.length].applyMatrix(this);
var pointB = this.lines[i].applyMatrix(this);
return pointA.subtract(pointB).normal().normalize();
};
CAL.Shape.prototype.collisionPoint = function (x, y) {
for (var i = 0; i < this.lines.length; i ++) {
if (new CAL.Vector(x, y).subtract(this.lines[i].applyMatrix(this)).dot(this.getNormal(i)) > 0) {
return false;
}
}
return true;
};
CAL.Shape.prototype.removePoint = function () {
for (var i = 0; i < arguments.length; i ++) {
var argument = arguments[i];
if (typeof argument === "number") {
var index = argument;
}
else if (argument instanceof CAL.BezierPoint || argument instanceof CAL.Vector) {
var index = this.getPointIndex(argument);
}
if (index !== -1) {
this.points.splice(index, 1);
}
}
this.update();
};
CAL.Shape.prototype.getPointIndex = function (point) {
if (point instanceof CAL.BezierPoint) {
return this.points.indexOf(argument);
}
else if (point instanceof CAL.Vector) {
for (i = 0; i < this.points.length; i ++) {
var length = this.points[i].position.subtract(point).length();
if (length === 0) {
return i;
}
}
return -1;
}
};
CAL.Shape.prototype.numberPoints = function () {
return this.points.length;
};
CAL.Shape.prototype.insertPoint = function (point, index) {
if (point instanceof CAL.Vector) {
point = new CAL.BezierPoint(point);
}
if (point instanceof CAL.BezierPoint) {
this.points.splice(index, 0, point);
this.update();
}
};
CAL.Shape.prototype.setClosed = function (closed) {
this.closed = closed;
this.update();
};
CAL.Shape.prototype.setPrecision = function (precision) {
this.precision = precision;
this.update();
};
CAL.Shape.prototype.makeSmooth = function (strength, start, end) {
start = start || 0;
end = end || this.points.length;
if (this.closed === false) {
start = Math.max(start, 1);
end = Math.min(end, this.points.length-1);
}
else {
start = Math.max(start, 0);
end = Math.min(end, this.points.length);
}
for (var i = start; i < end; i ++) {
var p0 = this.points[i-1];
var p1 = this.points[i];
var p2 = this.points[i+1];
if (typeof(p0) === "undefined") {
p0 = this.points[this.points.length-1];
}
else if (typeof(p2) === "undefined") {
p2 = this.points[0];
}
var a = p0.position.subtract(p1.position);
var b = p2.position.subtract(p1.position);
var length = (a.length()+b.length())/4;
var direction = a.normalize().add(b.scale(-1).normalize()).normalize();
p1.controlPointA = direction.scale(length*strength);
p1.controlPointB = direction.scale(-length*strength);
}
this.update();
};
CAL.Shape.prototype.calculateBezierPoints = function (p0, p1) {
var array = [];
if (p0.controlPointB.length() === 0 && p1.controlPointA.length() === 0) {
var angle = p1.position.subtract(p0.position).angle();
array.push({
x: p0.position.x,
y: p0.position.y,
angle: angle
}, {
x: p1.position.x,
y: p1.position.y,
angle: angle
});
}
else {
var point = {
x: p1.position.x,
y: p1.position.y,
angle: p1.controlPointB.direction()
}
array = array.concat(
recursiveBezier(
p0.position,
p0.position.add(p0.controlPointB),
p1.position.add(p1.controlPointA),
p1.position,
this.precision
),
[point]
);
}
return array;
};
CAL.Shape.prototype.update = function () {
if (this.points.length >= 2) {
if (this.points[0].controlPointB.length() !== 0) {
var point = {
x: this.points[0].position.x,
y: this.points[0].position.y,
angle: this.points[0].controlPointB.direction()
};
this.lines = [point];
}
for (var i = 0; i < this.points.length-1; i ++) {
var p0 = this.points[i];
var p1 = this.points[i+1];
this.lines = this.lines.concat(this.calculateBezierPoints(p0, p1));
}
if (this.closed === true) {
var p0 = this.points[this.points.length-1];
var p1 = this.points[0];
this.lines = this.lines.concat(this.calculateBezierPoints(p0, p1));
}
var pathLength = 0;
this.lines[0].position = 0;
for (var i = 0; i < this.lines.length-1; i ++) {
var p0 = this.lines[i];
var p1 = this.lines[i+1];
var line = new CAL.Vector(p1.x, p1.y).subtract(p0);
pathLength += line.length();
p1.position = pathLength;
}
this.length = pathLength;
}
};
CAL.Shape.prototype.getPosition = function (t) {
var t = Math.max(Math.min(t, this.length), 0);
for (var i = 1; true; i ++) {
var p0 = this.lines[i-1];
var p1 = this.lines[i];
if (p1.position >= t) {
t = (t-p0.position) / (p1.position-p0.position);
var position = new CAL.Vector(p0.x, p0.y).scale(1-t).add(new CAL.Vector(p1.x, p1.y).scale(t));
var angle = p0.angle + ((((p1.angle-p0.angle)%360)+540)%360-180)*t;
return {
x: position.x,
y: position.y,
angle: angle
};
}
}
};
CAL.Shape.prototype.setContextPart = function (context, begin, end) {
var beginPos = this.getPosition(begin);
var endPos = this.getPosition(end);
context.beginPath();
context.moveTo(beginPos.x, beginPos.y);
for (var i = 0; i < this.lines.length; i ++) {
var line = this.lines[i];
if (line.position > begin) {
context.lineTo(line.x, line.y);
if (line.position > end) {
break;
}
}
}
context.lineTo(endPos.x, endPos.y);
};
CAL.Shape.prototype.setContext = function (context) {
this.setContextPart(context, 0, this.length);
};
CAL.Shape.prototype.setClippingPart = function (context, begin, end) {
context.save();
this.setContextPart(begin, end);
context.clip();
};
CAL.Shape.prototype.setClipping = function (context) {
this.setClippingPart(context, 0, this.length);
};
CAL.Shape.prototype.drawPart = function (context, begin, end, color, width, cap) {
this.setContextPart(context, begin, end);
context.strokeStyle = color || "black";
context.lineWidth = width || 1;
context.lineCap = cap || 'butt';
context.stroke();
};
CAL.Shape.prototype.setContext = function (context, matrix) {
var matrix = matrix || this;
context.beginPath();
for (var i = 0; i < this.lines.length; i ++) {
var point = this.lines[i]//.applyMatrix(matrix);
context.lineTo(point.x, point.y);
}
if (this.closePath) {
context.closePath();
}
};
CAL.Shape.prototype.fill = function (context, matrix) {
this.setContext(context, matrix);
this.shapeColor.setFill(context);
context.fill();
};
CAL.Shape.prototype.stroke = function (context, matrix) {
this.setContext(context, matrix);
context.lineColor = this.lineColor;
context.lineWidth = this.lineWidth;
context.lineJoin = this.lineJoin;
context.lineCap = this.lineCap;
this.lineColor.setStroke(context);
context.stroke();
};
CAL.Shape.prototype.draw = function (context, matrix) {
if (this.shapeColor) {
this.shapeColor.setFill(context);
context.fill();
}
if (this.lineColor) {
this.stroke(context, matrix);
}
this.debugDraw(context);
};
CAL.Shape.prototype.debugDraw = function (context) {
context.beginPath();
context.lineWidth = 1;
for (var i = 0; i < this.points.length; i ++) {
var point = this.points[i];
point.draw(context);
}
context.beginPath();
for (var i = 0; i < this.lines.length; i ++) {
var point = this.lines[i];
context.lineTo(point.x, point.y);
context.arc(point.x, point.y, 2, 0, Math.PI*2, true);
context.lineTo(point.x, point.y);
}
context.strokeStyle = "black";
context.stroke();
};
CAL.Shape.prototype.getSize = function () {
var width = 0;
var height = 0;
for (var i = 0; i < this.lines.length; i ++) {
var line = this.lines[i];
width = Math.max(width, Math.ceil(line.x));
height = Math.max(width, Math.ceil(line.y));
}
return {
width: width,
height: height
};
};
CAL.BezierPoint = function (position, controlPointA, controlPointB) {
this.position = position || new CAL.Vector(0, 0);
this.controlPointA = controlPointA || new CAL.Vector(0, 0);
this.controlPointB = controlPointB || new CAL.Vector(0, 0);
}
CAL.BezierPoint.prototype.applyMatrix = function (matrix) {
var position = this.position.applyMatrix(matrix);
var controlPointA = this.controlPointA.applyMatrix(matrix);
var controlPointB = this.controlPointB.applyMatrix(matrix);
return new CAL.BezierPoint(position, controlPointA, controlPointB);
};
CAL.BezierPoint.prototype.draw = function (context) {
var leftHandle = this.controlPointA.add(this.position);
var rightHandle = this.controlPointB.add(this.position);
context.strokeStyle = "#09F";
context.beginPath();
context.moveTo(leftHandle.x, leftHandle.y);
context.lineTo(this.position.x, this.position.y);
context.lineTo(rightHandle.x, rightHandle.y);
context.stroke();
context.beginPath();
context.arc(this.position.x, this.position.y, 10, 0, Math.PI*2, true);
context.stroke();
context.beginPath();
context.arc(leftHandle.x, leftHandle.y, 5, 0, Math.PI*2, true);
context.stroke();
context.beginPath();
context.arc(rightHandle.x, rightHandle.y, 5, 0, Math.PI*2, true);
context.stroke();
};
function recursiveBezier (p1, p2, p3, p4, precision) {
//source: http://antigrain.com/research/adaptive_bezier/
//source: http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Derivative
var p12 = p1.add(p2).scale(0.5);
var p23 = p2.add(p3).scale(0.5);
var p34 = p3.add(p4).scale(0.5);
var p123 = p12.add(p23).scale(0.5);
var p234 = p23.add(p34).scale(0.5);
var p1234 = p123.add(p234).scale(0.5);
var d = p4.subtract(p1);
var d2 = Math.abs((p2.x - p4.x) * d.y - (p2.y - p4.y) * d.x);
var d3 = Math.abs((p3.x - p4.x) * d.y - (p3.y - p4.y) * d.x);
//if (Math.abs(p1.x + p3.x - p2.x - p2.x) + Math.abs(p1.y + p3.y - p2.y - p2.y) + Math.abs(p2.x + p4.x - p3.x - p3.x) + Math.abs(p2.y + p4.y - p3.y - p3.y) <= precision) {
if (Math.pow((d2 + d3), 2) < precision * d.dot(d)) {
//var t = 0.5;
//var derivative = (p2.subtract(p1)).scale(Math.pow(3*(1-t), 2)).add((p3.subtract(p2)).scale(6*(1-t)*t)).add((p4.subtract(p3)).scale(Math.pow(3*t, 2)));
var derivative = (p2.subtract(p1)).scale(2.25).add((p3.subtract(p2)).scale(1.5)).add((p4.subtract(p3)).scale(2.25));
var point = {
x: p1234.x,
y: p1234.y,
angle: derivative.direction()
}
return [point];
}
else {
return [].concat(
recursiveBezier(p1, p12, p123, p1234, precision),
recursiveBezier(p1234, p234, p34, p4, precision)
);
}
}*/
CAL.Text = function (options) {
options = options || {};
CAL.Matrix.call(this, options);
this.visible = options.visible !== undefined ? options.visible : true;
this.active = options.active !== undefined ? options.active : true;
this.depth = options.depth || 0;
this.text = options.text || "";
this.style = options.style || "normal";
this.variant = options.variant || "normal";
this.weight = options.weight || "normal";
this.size = options.size || 12;
this.font = options.font || "Arial";
this.textAlign = options.textAlign || "left";
this.color = options.color || new CAL.Color();
this.alpha = typeof options.alpha === "number" ? options.alpha : 1;
};
CAL.Text.prototype = Object.create(CAL.Matrix.prototype);
CAL.Text.prototype.drawText = function (context, text, x, y) {
context.font = [this.style, this.variant, this.weight, this.size+"px", this.font].join(" ");
context.textAlign = this.textAlign;
this.color.setColor(context);
context.fillText(text, x, y);
};
CAL.Text.prototype.drawTextAlpha = function (context, text, x, y, apha) {
context.font = [this.style, this.variant, this.weight, this.size+"px", this.font].join(" ");
context.globalAlpha = apha;
this.color.setColor(context);
context.fillText(text, x, y);
context.globalAlpha = 1;
};
CAL.Text.prototype.draw = function (context, matrix) {
context.save();
matrix.setContext(context);
context.globalAlpha = this.alpha;
this.drawText(context, this.text, 0, 0);
context.restore();
};
CAL.Text.prototype.clone = function () {
return new CAL.text({
style : this.style,
variant : this.variant,
weight : this.weight,
size : this.size,
font : this.font,
color : this.color.clone()
});
};
CAL.KeyListener = function (options) {
options = options || {};
this.visible = false;
this.active = options.active !== undefined ? option.active : true;
this.depth = -10000;
this.actions = options.actions || {};
CAL.Scene.add(this);
};
CAL.KeyListener.prototype.add = function (key, callback) {
this.actions[key] = callback;
};
CAL.KeyListener.prototype.keyDown = function (key) {
if (this.actions[key]) {
this.actions[key]();
}
};
CAL.Physics = function () {
CAL.Scene.add(this);
this.depth = -1000;
this.active = true;
this.visible = false;
this.objects = [];
this.manifolds = [];
this.forces = [];
}
CAL.Physics.prototype.keyDown = function () {
//DEBUG REASONS
//EASIER TO DEBUG ON KEYDOWN
CAL.Scene.clear();
this.test(120);
polygon.debugDraw(CAL.Scene.context);
box.debugDraw(CAL.Scene.context);
};
CAL.Physics.prototype.add = function () {
for (var i = 0; i < arguments.length; i ++) {
var object = arguments[i];
if (object instanceof CAL.PhysicsObject && this.objects.indexOf(object) === -1) {
for (var j = 0; j < this.objects.length; j ++) {
this.manifolds.push(new CAL.Manifold(object, this.objects[j]));
this.manifolds.push(new CAL.Manifold(this.objects[j], object));
}
this.objects.push(object);
}
else if (object instanceof CAL.Force && this.forces.indexOf(object) === -1) {
this.forces.push(object);
}
}
};
CAL.Physics.prototype.test = function (dt) {
//var dt = 120;
this.objects.foreach(function (object) {
object.step(dt);
}, this);
this.forces.foreach(function (force) {
force.step(dt);
}, this);
this.manifolds.foreach(function (manifold) {
manifold.step(dt);
}, this);
};
CAL.Manifold = function (a, b) {
this.a = a;
this.b = b;
this.normal;
};
CAL.Manifold.prototype.checkCollision = function (dt) {
var collisionData = {collision: false};
this.b.shape.points.foreach(function (pointA, i) {
var pointA = pointA.applyMatrix(this.b.shape);
var collision = true;
var bestDistance = -Infinity;
var bestNormal;
this.a.shape.points.foreach(function (pointB, j) {
var pointB = pointB.applyMatrix(this.a.shape);
var normal = this.a.shape.getNormal(j);
var distance = normal.dot(pointA.subtract(pointB));
if (distance > 0) {
collision = false;
return true;
}
else if (distance > bestDistance) {
bestDistance = distance;
bestNormal = normal;
}
}, this);
if (collision) {
collisionData = {collision: true, penetrationDepth: -bestDistance, normal: bestNormal, impactPoint: pointA};
return true;
}
}, this);
return collisionData;
/*this.a.shape.points.foreach(function (point, i) {
var normal = this.a.shape.getNormal(i);
var support = this.b.getSupport(normal.scale(-1));
var point = point.applyMatrix(this.a.shape);
var distance = normal.dot(support.subtract(point));
//if (distance > 0) {
// console.log("test");
// bestDistance = 1;
// return true;
//}
if (distance > bestDistance || bestDistance === false) {
//var velocity = this.b.velocity.add(this.b.getPointVelocity(support));
//if (velocity.dot(normal) < 0 || velocity.length === 0) {
bestDistance = distance;
bestNormal = normal;
bestImpactPoint = support;
//bestVelocity = velocity;
//}
}
}, this);
var i = 2;
var normal = this.a.shape.getNormal(i);
normal.scale(100).draw(CAL.Scene.context, 200, 200);
var support = this.b.getSupport(normal.scale(-1));
var point = this.a.shape.points[i].applyMatrix(this.a.shape);
var distance = normal.dot(support.subtract(point));
console.log(distance);
CAL.Scene.context.beginPath();
CAL.Scene.context.arc(support.x, support.y, 10, 0, Math.PI*2);
CAL.Scene.context.stroke();
return {collision: bestDistance < 0, penetrationDepth: bestDistance, normal: bestNormal, impactPoint: bestImpactPoint};*/
};
CAL.Manifold.prototype.positionalCorrection = function (collisionData) {
var percent = 0.2; // usually 20% to 80%
var slop = 0.1; // usually 0.01 to 0.1
var correction = collisionData.normal.scale(Math.max(collisionData.penetrationDepth - slop, 0) / (1/this.a.mass + 1/this.b.mass) * percent);
var aPos = correction.scale(1/this.a.mass);
this.a.shape._x += aPos.x;
this.a.shape._y += aPos.y;
this.a.shape.updateMatrix();
var bPos = correction.scale(1/this.b.mass);
this.b.shape._x -= bPos.x;
this.b.shape._y -= bPos.y;
this.b.shape.updateMatrix();
};
CAL.Manifold.prototype.resolveCollision = function (collisionData) {
var restVelocity = this.b.velocity.subtract(this.a.velocity);
var velAlongNormal = restVelocity.dot(collisionData.normal);
if (velAlongNormal > 0) {
return;
}
var restitution = Math.min(this.a.restitution, this.b.restitution);
var force = -(1 + restitution) * velAlongNormal / (1/this.a.mass + 1/this.b.mass);
var impulse = collisionData.normal.scale(force);
this.a.addForce(impulse.scale(-1), collisionData.impactPoint);
this.b.addForce(impulse, collisionData.impactPoint);
};
CAL.Manifold.prototype.step = function (dt) {
var collisionData = this.checkCollision();
if (collisionData.collision) {
this.resolveCollision(collisionData);
this.positionalCorrection(collisionData);
}
};
CAL.PhysicsObject = function (options) {
options = options || {};
this.shape = options.shape || new CAL.Shape();
this.velocity = options.velocity || new CAL.Vector();
this.angularVelocity = options.angularVelocity !== undefined ? options.angularVelocity : 0;
this.restitution = options.restitution !== undefined ? options.restitution : 0.5;
this.density = options.density || 1;
this.updateMass();
};
CAL.PhysicsObject.prototype.calculateBoundingBox = function (matrix) {
matrix = matrix || this.shape;
var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;
this.shape.points.foreach(function (point) {
var point = point.applyMatrix(matrix);
minX = (point.x < minX) ? point.x : minX;
minY = (point.y < minY) ? point.y : minY;
maxX = (point.x > maxX) ? point.x : maxX;
maxY = (point.y > maxY) ? point.y : maxY;
}, this);
return {minX: minX, minY: minY, maxX: maxX, maxY: maxY};
};
CAL.PhysicsObject.prototype.addForce = function (impulse, contactVector) {
this.velocity = this.velocity.add(impulse.scale(1.0/this.mass));
/*if (contactVector !== undefined) {
this.angularVelocity += contactVector.subtract(this.centerOfMass.applyMatrix(this.shape)).cross(impulse)*(1.0/this.inertia);
//this.angularVelocity = contactVector.subtract(this.centerOfMass.applyMatrix(this.shape)).cross(impulse)*(1.0/this.inertia);
}*/
};
CAL.PhysicsObject.prototype.pointCollision = function (point) {
var point = point
.subtract(new CAL.Vector(this.shape.x, this.shape.y))
.rotate(-this.shape.rotation)
.multiply(new CAL.Vector(1/this.shape.sx, 1/this.shape.sy))
.subtract(new CAL.Vector(this.collisionMask.offsetX, this.collisionMask.offsetY));
if (point.x < 0) return false;
if (point.y < 0) return false;
if (point.x > this.collisionMask.width) return false;
if (point.y > this.collisionMask.height) return false;
var i = Math.round(point.y)*this.collisionMask.width + Math.round(point.x);
return this.collisionMask.data[i];
};
CAL.PhysicsObject.prototype.setDensity = function (density) {
this.density = density;
this.mass = this.area*density;
this.inertia = this.mass*this.area;
};
CAL.PhysicsObject.prototype.updateMass = function () {
var boundingBox = this.calculateBoundingBox(new CAL.Matrix());
var surface = new CAL.Surface({x: boundingBox.maxX - boundingBox.minX, y: boundingBox.maxY - boundingBox.minY});
this.shape.clip(surface.context, new CAL.Matrix({x: -boundingBox.minX, y: -boundingBox.minY}));
surface.context.fillStyle = "black";
surface.context.fillRect(0, 0, surface.width, surface.height);
var imageData = surface.getImageData();
var point = new CAL.Vector();
var area = 0;
this.collisionMask = {
data: [],
width: imageData.width,
height: imageData.height,
offsetX: boundingBox.minX,
offsetY: boundingBox.minY
};
for (var i = 0; i < imageData.data.length; i += 4) {
var alpha = imageData.data[i + 3];
if (alpha > 0) {
point = point.add(new CAL.Vector(i/4 % imageData.width, Math.floor(i/4/imageData.width)));
area ++;
this.collisionMask.data.push(true);
}
else {
this.collisionMask.data.push(false);
}
}
this.area = area/**this.sx*this.sy*/;
this.mass = area*this.density;
this.inertia = this.mass*area;
this.centerOfMass = point.scale(1/area).add(new CAL.Vector(boundingBox.minX, boundingBox.minY));
};
CAL.PhysicsObject.prototype.getSupport = function (n) {
var bestProjection = -Infinity;
var bestPoint;
for (var i = 0; i < this.shape.points.length; i ++) {
var point = this.shape.points[i].applyMatrix(this.shape);
var projection = point.dot(n);
if (projection > bestProjection) {
bestPoint = point;
bestProjection = projection;
}
}
return bestPoint;
};
CAL.PhysicsObject.prototype.getPointVelocity = function (point) {
var relativePoint = point.subtract(this.centerOfMass.applyMatrix(this.shape));
var rotatedPoint = relativePoint.rotate(this.angularVelocity);
return rotatedPoint.subtract(relativePoint);
};
CAL.PhysicsObject.prototype.findCollisionFace = function (point) {
};
CAL.PhysicsObject.prototype.step = function (dt) {
//this.rotateAroundRelative(this.torque*dt, this.centerOfMass);
//this.angularVelocity += torque * (1.0 / this.inertia) * dt
/*if (this.velocity.length < 0.0001) {
this.velocity = new CAL.Vector();
}
if (Math.abs(this.angularVelocity) < 0.00001) {
this.angularVelocity = 0;
}*/
this.shape._x += this.velocity.x*dt;
this.shape._y += this.velocity.y*dt;
this.shape.updateMatrix();
//this.shape.rotateAroundRelative(this.angularVelocity*dt, this.centerOfMass);
};
CAL.PhysicsObject.prototype.debugDraw = function (context) {
context.lineWidth = 1;
context.strokeStyle = "black";
var minX = Infinity;
var minY = Infinity;
var maxX = -Infinity;
var maxY = -Infinity;
context.beginPath();
this.shape.points.foreach(function (point) {
var point = point.applyMatrix(this.shape);
context.lineTo(point.x, point.y);
context.arc(point.x, point.y, 3, 0, 2*Math.PI*2);
context.lineTo(point.x, point.y);
minX = (point.x < minX) ? point.x : minX
minY = (point.y < minY) ? point.y : minY
maxX = (point.x > maxX) ? point.x : maxX
maxY = (point.y > maxY) ? point.y : maxY
}, this);
context.closePath();
context.stroke();
this.shape.points.foreach(function (point, i) {
var point = point.applyMatrix(this.shape);
var text = new CAL.Text();
text.drawText(context, i, point.x + 10, point.y - 10);
//this.getPointVelocity(point).scale(1000).draw(context, point.x, point.y);
}, this);
context.beginPath();
context.moveTo(minX, minY);
context.lineTo(minX, maxY);
context.lineTo(maxX, maxY);
context.lineTo(maxX, minY);
context.closePath();
var centerOfMass = this.centerOfMass.applyMatrix(this.shape);
context.moveTo(centerOfMass.x-5, centerOfMass.y-5);
context.lineTo(centerOfMass.x+5, centerOfMass.y+5);
context.moveTo(centerOfMass.x+5, centerOfMass.y-5);
context.lineTo(centerOfMass.x-5, centerOfMass.y+5);
context.strokeStyle = "red";
context.stroke();
var center = this.centerOfMass.applyMatrix(this.shape);
this.velocity.scale(1000).draw(context, center.x, center.y);
};
CAL.Force = function (options) {
this.objects = options.objects || [];
this.velocity = options.velocity || new CAL.Vector();
}
CAL.Force.prototype.add = function () {
for (var i = 0; i < arguments.length; i ++) {
var argument = arguments[i];
this.objects.push(argument);
}
};
CAL.Force.prototype.step = function (dt) {
for (var i = 0; i < this.objects.length; i ++) {
var object = this.objects[i];
object.velocity = object.velocity.add(this.velocity.scale(dt));
}
};