summaryrefslogtreecommitdiff
path: root/assets/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'assets/javascripts')
-rw-r--r--assets/javascripts/math/point.js227
-rw-r--r--assets/javascripts/math/util.js256
-rw-r--r--assets/javascripts/math/vec2.js237
-rw-r--r--assets/javascripts/vendor/canvasutilities.js145
-rw-r--r--assets/javascripts/vendor/tube.js323
5 files changed, 1188 insertions, 0 deletions
diff --git a/assets/javascripts/math/point.js b/assets/javascripts/math/point.js
new file mode 100644
index 0000000..354be00
--- /dev/null
+++ b/assets/javascripts/math/point.js
@@ -0,0 +1,227 @@
+(function(){
+ function clamp(n,a,b){ return n<a?a:n<b?n:b }
+
+ var point = function (a,b){
+ this.a = a
+ this.b = b
+ }
+ point.prototype.magnitude = function(){
+ return this.b-this.a
+ }
+ point.prototype.length = function(){
+ return Math.abs(this.b-this.a)
+ }
+ point.prototype.dist = function(){
+ return dist(0,this.a,0,this.b)
+ }
+ point.prototype.clone = function(){
+ return new point(this.a, this.b)
+ }
+ point.prototype.assign = function(v){
+ this.a = v.a
+ this.b = v.b
+ return this
+ }
+ point.prototype.abs = function(){
+ if (this.b < this.a) {
+ this.invert()
+ }
+ return this
+ }
+ point.prototype.invert = function(){
+ this.a = this.a ^ this.b
+ this.b = this.a ^ this.b
+ this.a = this.a ^ this.b
+ return this
+ }
+ point.prototype.midpoint = function(){
+ return lerp(0.5, this.a, this.b)
+ }
+ point.prototype.lerp = function(n){
+ return lerp(n, this.a, this.b)
+ }
+ point.prototype.eq = function(v){
+ return this.a == v.a && this.b == v.b
+ }
+ point.prototype.add = function(n){
+ this.a += n
+ this.b += n
+ return this
+ }
+ point.prototype.sub = function(n){
+ this.a -= n
+ this.b -= n
+ return this
+ }
+ point.prototype.mul = function(n){
+ this.a *= n
+ this.b *= n
+ return this
+ }
+ point.prototype.div = function(n){
+ this.a /= n
+ this.b /= n
+ return this
+ }
+ point.prototype.addVec = function(v){
+ this.a += v.a
+ this.b += v.b
+ return this
+ }
+ point.prototype.subVec = function(v){
+ this.a -= v.a
+ this.b -= v.b
+ return this
+ }
+ point.prototype.zero = function(){
+ this.a = this.b = 0
+ }
+ point.prototype.round = function(){
+ this.a = Math.round(this.a)
+ this.b = Math.round(this.b)
+ }
+ point.prototype.setPosition = function(n){
+ var len = this.length()
+ this.a = n
+ this.b = n + len
+ }
+ point.prototype.setLength = function(n){
+ this.b = this.a + n
+ }
+ point.prototype.normalize = function(){
+ var dim = max(this.a, this.b)
+ this.a = this.a/dim
+ this.b = this.b/dim
+ return this
+ }
+ point.prototype.contains = function(n){
+ return this.a <= n && n <= this.b
+ }
+ point.prototype.containsCenter = function(n){
+ return this.a < n && n < this.b
+ }
+ point.prototype.containsDisc = function(n,r){
+ return this.a <= n-r && n+r <= this.b
+ }
+ point.prototype.clamp = function(n){
+ return clamp(n, this.a, this.b)
+ }
+ point.prototype.clampDisc = function(n,r){
+ return clamp(n, this.a+r, this.b-r)
+ }
+ point.prototype.intersects = function(v){
+ if (this.a == v.a || this.b == v.b || this.a == v.b || this.b == v.a) {
+ return true
+ }
+ else if (this.a < v.a) {
+ return (v.a < this.b && this.b <= v.b) || (this.a < v.b && v.b <= this.b)
+ }
+ else if (this.a > v.a) {
+ return (this.a < v.b && v.b <= this.b) || (v.a < this.b && this.b <= v.b)
+ }
+ }
+ point.prototype.overlaps = function(v){
+ if (this.a == v.a || this.b == v.b) {
+ return true
+ }
+ if (this.a < v.a) {
+ return (v.a < this.b && this.b < v.b) || (this.a < v.b && v.b < this.b)
+ }
+ else if (v.a < this.a) {
+ return (this.a < v.b && v.b < this.b) || (v.a < this.b && this.b < v.b)
+ }
+ }
+
+
+ point.prototype.adjacent = function(v){
+ if (this.a == v.a || this.b == v.b || this.a == v.b || this.b == v.a) {
+ return true
+ }
+ return false
+ }
+ point.prototype.union = function(v){
+ if (this.intersects(v)) {
+ return new point( Math.min(this.a,v.a), Math.max(this.b, v.b) )
+ }
+ }
+ point.prototype.intersection = function(v){
+ if (this.intersects(v)) {
+ return new point( Math.max(this.a,v.a), Math.min(this.b, v.b) )
+ }
+ }
+
+ // given two vectors, test how they overlap
+ // return the set of overlapping segments in the initial vector, labelled with sides
+ point.prototype.split = function(v, left, right){
+ var intervals = [], _len
+
+ if (this.eq(v)) {
+ intervals.push([ new point( this.a, this.b ), left | right ])
+ }
+
+ // a---A===b---B (rightways overlap)
+ else if (this.contains(v.a) && v.contains(this.b)) {
+ intervals.push([ new point( this.a, v.a ), left ])
+ intervals.push([ new point( v.a, this.b ), right ])
+ }
+
+ // A---a===B---b (leftways overlap)
+ else if (v.contains(this.a) && this.contains(v.b)) {
+ intervals.push([ new point( this.a, v.b ), left ])
+ intervals.push([ new point( v.b, this.b ), right ])
+ }
+
+ // a---A===B---b (contains v)
+ else if (this.contains(v.a) && this.contains(v.b)) {
+ intervals.push([ new point( this.a, v.a ), left ])
+ intervals.push([ new point( v.a, v.b ), 0 ])
+ intervals.push([ new point( v.b, this.b ), right ])
+ }
+
+ // A---a===b---B (contained in v)
+ else { // if (v.contains(this.a) && v.contains(v.b)) {
+ intervals.push([ new point( this.a, this.b ), left | right ])
+ }
+
+ // cull empty vectors
+ _len = intervals.length
+ if (_len > 1) {
+ if (intervals[0][0].magnitude() == 0) {
+ intervals[1][1] |= intervals[0][1]
+ intervals.shift()
+ }
+ else if (intervals[_len-1][0].magnitude() == 0) {
+ intervals[_len-2][1] |= intervals[_len-1][1]
+ intervals.pop()
+ }
+ }
+
+ return intervals
+ }
+
+ point.prototype.toString = function(){
+ return "[" + round(this.a) + " " + round(this.b) + "]"
+ }
+ point.prototype.exactString = function(){
+ return "[" + this.a + " " + this.b + "]"
+ }
+ point.prototype.serialize = function(){
+ return [ round(this.a), round(this.b) ]
+ }
+ point.prototype.deserialize = function(data){
+ this.a = data[0]
+ this.b = data[1]
+ }
+ point.prototype.quantize = function(n){
+ n = n || 10
+ this.a = quantize(this.a, n)
+ this.b = quantize(this.b, n)
+ }
+
+ if ('window' in this) {
+ window.point = point
+ }
+ else {
+ module.exports = point
+ }
+})()
diff --git a/assets/javascripts/math/util.js b/assets/javascripts/math/util.js
new file mode 100644
index 0000000..609bdd6
--- /dev/null
+++ b/assets/javascripts/math/util.js
@@ -0,0 +1,256 @@
+if (window.$) {
+ $.fn.int = function() { return parseInt($(this).val(),10) }
+ $.fn.float = function() { return parseFloat($(this).val()) }
+ $.fn.string = function() { return trim($(this).val()) }
+ $.fn.enable = function() { return $(this).attr("disabled",null) }
+ $.fn.disable = function() { return $(this).attr("disabled","disabled") }
+ $.fn.sanitize = function(s) { return trim(sanitize($(this).val())) }
+ $.fn.htmlSafe = function(s) { return $(this).html(sanitize(s)) }
+}
+
+function trim (s){ return s.replace(/^\s+/,"").replace(/\s+$/,"") }
+function sanitize (s){ return (s || "").replace(new RegExp("[<>&]", 'g'), "") }
+function stripHTML (s){ return (s || "").replace(/<[^>]+>/g, "") }
+function capitalize (s){ return s.split(" ").map(capitalizeWord).join(" ") }
+function capitalizeWord (s){ return s.charAt(0).toUpperCase() + s.slice(1) }
+function slugify (s){ return (s || "").toLowerCase().replace(/\s/g,"-").replace(/[^-_a-zA-Z0-9]/g, '-').replace(/-+/g,"-") }
+function rgb_string (rgb) { return "rgb(" + rgb.map(Math.round).join(",") + ")" }
+function rgba_string (rgb,a) { return "rgba(" + rgb.map(Math.round).join(",") + "," + a + ")" }
+function hex_string (rgb) { return "#" + rgb.map(Math.round).map(function(n){ var s = n.toString(16); return s.length == 1 ? "0"+s : s }).join("") }
+function parse_rgba_string (s) { return s.match(/(\d+)/g).slice(0,3) }
+
+var E = Math.E
+var PI = Math.PI
+var PHI = (1+Math.sqrt(5))/2
+var TWO_PI = PI*2
+var HALF_PI = PI/2
+var LN10 = Math.LN10
+function clamp(n,a,b){ return n<a?a:n<b?n:b }
+function norm(n,a,b){ return (n-a) / (b-a) }
+function lerp(n,a,b){ return (b-a)*n+a }
+function mix(n,a,b){ return a*(1-n)+b*n }
+function ceil(n){ return Math.ceil(n) }
+function floor(n){ return Math.floor(n) }
+function round(n){ return Math.round(n) }
+function quantize(n,a){ return round(n / a) * a }
+function max(a,b){ return Math.max(a,b) }
+function min(a,b){ return Math.min(a,b) }
+function abs(n){ return Math.abs(n) }
+function sign(n){ return n ? Math.abs(n)/n : 0 }
+function pow(n,b) { return Math.pow(n,b) }
+function exp(n) { return Math.exp(n) }
+function log(n){ return Math.log(n) }
+function ln(n){ return Math.log(n)/LN10 }
+function sqrt(n) { return Math.sqrt(n) }
+function cos(n){ return Math.cos(n) }
+function sin(n){ return Math.sin(n) }
+function tan(n){ return Math.tan(n) }
+function acos(n){ return Math.cos(n) }
+function asin(n){ return Math.sin(n) }
+function atan(n){ return Math.atan(n) }
+function atan2(a,b){ return Math.atan2(a,b) }
+function sec(n){ return 1/cos(n) }
+function csc(n){ return 1/sin(n) }
+function cot(n){ return 1/tan(n) }
+function cosp(n){ return (1+Math.cos(n))/2 } // cos^2
+function sinp(n){ return (1+Math.sin(n))/2 }
+function random(){ return Math.random() }
+function rand(n){ return (Math.random()*n) }
+function randint(n){ return rand(n)|0 }
+function randrange(a,b){ return a + rand(b-a) }
+function choice(a){ return a[randint(a.length)] }
+function deg(n){ return n*180/PI }
+function rad(n){ return n*PI/180 }
+function xor(a,b){ a=!!a; b=!!b; return (a||b) && !(a&&b) }
+function mod(n,m){ return n-(m * floor(n/m)) }
+function dist(x0,y0,x1,y1){ return sqrt(pow(x1-x0,2)+pow(y1-y0,2)) }
+function angle(x0,y0,x1,y1){ return atan2(y1-y0,x1-x0) }
+function avg(m,n,a){ return (m*(a-1)+n)/a }
+function noop(){}
+
+function pixel(x,y){ return 4*(mod(y,actual_h)*actual_w+mod(x,actual_w)) }
+function rgbpixel(d,x,y){
+ var p = pixel(~~x,~~y)
+ r = d[p]
+ g = d[p+1]
+ b = d[p+2]
+ a = d[p+3]
+}
+function fit(d,x,y){ rgbpixel(d,x*actual_w/w,y*actual_h/h) }
+
+function step(a, b){
+ return (b >= a) + 0
+ // ^^ bool -> int
+}
+
+function julestep (a,b,n) {
+ return clamp(norm(n,a,b), 0.0, 1.0);
+}
+
+// hermite curve apparently
+function smoothstep(min,max,n){
+ var t = clamp((n - min) / (max - min), 0.0, 1.0);
+ return t * t * (3.0 - 2.0 * t)
+}
+
+function shuffle(a){
+ var r, swap
+ for (var i = a.length; i > 0; i--){
+ r = randint(i)
+ swap = a[i-1]
+ a[i-1] = a[r]
+ a[r] = swap
+ }
+ return a
+}
+function reverse(a){
+ var reversed = []
+ for (var i = 0, _len = a.length-1; i <= _len; i++){
+ reversed[i] = a[_len-i]
+ }
+ return reversed
+}
+function deinterlace(a){
+ var odd = [], even = []
+ for (var i = 0, _len = a.length; i < _len; i++) {
+ if (i % 2) even.push(a[i])
+ else odd.push(a[i])
+ }
+ return [even, odd]
+}
+function weave(a){
+ var aa = deinterlace(a)
+ var b = []
+ aa[0].forEach(function(el){ b.push(el) })
+ reverse(aa[1]).forEach(function(el){ b.push(el) })
+ return b
+}
+function range(m,n,s){
+ var a = []
+ s = s || 1
+ for (var i = m; i <= n; i += s) {
+ a.push(i)
+ }
+ return a
+}
+
+var guid_syllables = "iz az ez or iv ex baz el lo lum ot un no".split(" ")
+var guid_n = 0
+function guid(n){
+ var len = guid_syllables.length
+ return ((++guid_n*(len-1)*(~~log(guid_n))).toString(len)).split("").map(function(s){
+ return guid_syllables[parseInt(s, len) % len--]
+ }).join("")
+}
+
+function defaults (dest, src) {
+ dest = dest || {}
+ for (var i in src) {
+ dest[i] = typeof dest[i] == 'undefined' ? src[i] : dest[i]
+ }
+ return dest
+}
+
+// Change straight quotes to curly and double hyphens to em-dashes.
+function smarten(a) {
+ a = a.replace(/(^|[-\u2014\s(\["])'/g, "$1\u2018"); // opening singles
+ a = a.replace(/'/g, "\u2019"); // closing singles & apostrophes
+ a = a.replace(/(^|[-\u2014/\[(\u2018\s])"/g, "$1\u201c"); // opening doubles
+ a = a.replace(/"/g, "\u201d"); // closing doubles
+ a = a.replace(/--/g, "\u2014"); // em-dashes
+ return a
+};
+
+
+function pairs(h){
+ var a = []
+ for (var i in h) {
+ if(h.hasOwnProperty(i)) {
+ a.push([i, h[i]])
+ }
+ }
+ return a
+}
+function invert_hash (h) {
+ var k = {}
+ for (var i in h) { if (h.hasOwnProperty(i)) k[h[i]] = i }
+ return k
+}
+function filenameFromUrl (url) {
+ var partz = url.split( "/" )
+ return partz[partz.length-1].split(".")[0]
+}
+
+function bitcount(v) {
+ v = v - ((v >>> 1) & 0x55555555);
+ v = (v & 0x33333333) + ((v >>> 2) & 0x33333333);
+ return ((v + (v >>> 4) & 0xF0F0F0F) * 0x1010101) >>> 24;
+}
+
+// Function.bind polyfill
+if (!Function.prototype.bind) {
+ Function.prototype.bind = function(oThis) {
+ if (typeof this !== 'function') {
+ // closest thing possible to the ECMAScript 5
+ // internal IsCallable function
+ throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
+ }
+
+ var aArgs = Array.prototype.slice.call(arguments, 1),
+ fToBind = this,
+ fNOP = function() {},
+ fBound = function() {
+ return fToBind.apply(this instanceof fNOP && oThis
+ ? this
+ : oThis,
+ aArgs.concat(Array.prototype.slice.call(arguments)));
+ };
+
+ fNOP.prototype = this.prototype;
+ fBound.prototype = new fNOP();
+
+ return fBound;
+ };
+}
+
+// rAF polyfill
+(function() {
+ var lastTime = 0;
+ var vendors = ['ms', 'moz', 'webkit', 'o'];
+ for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
+ window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
+ window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
+ || window[vendors[x]+'CancelRequestAnimationFrame'];
+ }
+
+ if (!window.requestAnimationFrame)
+ window.requestAnimationFrame = function(callback, element) {
+ var currTime = new Date().getTime();
+ var timeToCall = Math.max(0, 16 - (currTime - lastTime));
+ var id = window.setTimeout(function() { callback(currTime + timeToCall); },
+ timeToCall);
+ lastTime = currTime + timeToCall;
+ return id;
+ };
+
+ if (!window.cancelAnimationFrame)
+ window.cancelAnimationFrame = function(id) {
+ clearTimeout(id);
+ };
+}());
+
+
+function selectElementContents(el) {
+ if (window.getSelection && document.createRange) {
+ var sel = window.getSelection();
+ var range = document.createRange();
+ range.selectNodeContents(el);
+ sel.removeAllRanges();
+ sel.addRange(range);
+ } else if (document.selection && document.body.createTextRange) {
+ var textRange = document.body.createTextRange();
+ textRange.moveToElementText(el);
+ textRange.select();
+ }
+}
+
diff --git a/assets/javascripts/math/vec2.js b/assets/javascripts/math/vec2.js
new file mode 100644
index 0000000..3e1f463
--- /dev/null
+++ b/assets/javascripts/math/vec2.js
@@ -0,0 +1,237 @@
+
+(function(){
+ var point
+ if ('window' in this) {
+ point = window.point
+ }
+ else {
+ point = require('./point')
+ FRONT = 0x1, BACK = 0x2, LEFT = 0x4, RIGHT = 0x8, FLOOR = 0x10, CEILING = 0x20
+ TOP = CEILING, BOTTOM = FLOOR
+ function sidesToString(sides){
+ var s = ""
+ if (sides & FRONT) s += "front "
+ if (sides & BACK) s += "back "
+ if (sides & LEFT) s += "left "
+ if (sides & RIGHT) s += "right "
+ if (sides & TOP) s += "top "
+ if (sides & BOTTOM) s += "bottom "
+ return s
+ }
+ }
+
+ var vec2 = function (x0,y0,x1,y1){
+ if (x0 instanceof point) {
+ this.x = x0
+ this.y = y0
+ }
+ else if (x1 === undefined) {
+ this.x = new point(x0,x0)
+ this.y = new point(y0,y0)
+ }
+ else {
+ this.x = new point(x0,x1)
+ this.y = new point(y0,y1)
+ }
+ this.translation = new point(0,0)
+ this.sides = FRONT | BACK | LEFT | RIGHT
+ }
+ vec2.prototype.clone = function(){
+ return new vec2( this.x.clone(), this.y.clone() )
+ }
+ vec2.prototype.assign = function(r) {
+ this.x.assign(r.x)
+ this.y.assign(r.y)
+ return this
+ }
+ vec2.prototype.center = function(){
+ return new point(this.x.midpoint(), this.y.midpoint())
+ }
+ vec2.prototype.area = function(){
+ return this.x.length() * this.y.length()
+ }
+ vec2.prototype.magnitude = function(){
+ return dist(this.x.a, this.y.a, this.x.b, this.y.b)
+ }
+ vec2.prototype.maxDimension = function(){
+ return abs(this.width) > abs(this.height) ? this.width : this.height
+ }
+
+ vec2.prototype.mul = function(n){
+ this.x.mul(n)
+ this.y.mul(n)
+ return this
+ }
+ vec2.prototype.div = function(n){
+ this.x.div(n)
+ this.y.div(n)
+ return this
+ }
+
+ vec2.prototype.translate = function(translation){
+ var translation = translation || this.translation
+ this.x.abs().add(translation.a)
+ this.y.abs().add(translation.b)
+ this.translation.a = this.translation.b = 0
+ return this
+ }
+ vec2.prototype.resize = function(translation, sides){
+ var translation = translation || this.translation
+ sides = sides || translation.sides
+
+ if (sides & LEFT) {
+ this.x.a += translation.a
+ }
+ if (sides & RIGHT) {
+ this.x.b += translation.a
+ }
+ if (sides & FRONT) {
+ this.y.a += translation.b
+ }
+ if (sides & BACK) {
+ this.y.b += translation.b
+ }
+ this.translation.a = this.translation.b = 0
+ }
+ vec2.prototype.contains = function(x,y){
+ return this.x.contains(x) && this.y.contains(y)
+ }
+ vec2.prototype.contains_point = function(p){
+ return this.x.contains(p.x) && this.y.contains(p.y)
+ }
+ vec2.prototype.containsDisc = function(x,y,r){
+ return this.x.containsDisc(x,r) && this.y.containsDisc(y,r)
+ }
+ vec2.prototype.overlaps = function(rect){
+ return this.x.overlaps(rect.x) && this.y.overlaps(rect.y)
+ }
+ vec2.prototype.intersects = function(r){
+ var corner_intersect = (this.x.b === r.x.a && this.y.b === r.y.a)
+ return this.x.intersects(r.x) && this.y.intersects(r.y) && ! corner_intersect
+ }
+ vec2.prototype.adjacent = function(r){
+ return this.x.adjacent(r.x) && this.y.adjacent(r.y)
+ }
+ vec2.prototype.eq = function(r){
+ return this.x.eq(r.x) && this.y.eq(r.y)
+ }
+ vec2.prototype.fits = function(v){
+ return this.x.length() >= v.a && this.y.length() >= v.b
+ }
+ vec2.prototype.zero = function(){
+ this.a.zero()
+ this.b.zero()
+ }
+ vec2.prototype.nearEdge = function (x, y, r) {
+ var edges = 0
+ if (x < this.x.a+r) {
+ edges |= LEFT
+ }
+ else if (x > this.x.b-r) {
+ edges |= RIGHT
+ }
+ if (y < this.y.a+r) {
+ edges |= FRONT
+ }
+ else if (y > this.y.b-r) {
+ edges |= BACK
+ }
+ return edges
+ }
+ vec2.prototype.width = function(){ return this.x.length() }
+ vec2.prototype.height = function(){ return this.y.length() }
+ vec2.prototype.delta = function(){ return new point( this.x.magnitude(), this.y.magnitude() ) }
+ vec2.prototype.expand = function(rect){
+ this.x.a = Math.min( this.x.a, rect.x.a )
+ this.x.b = Math.max( this.x.b, rect.x.b )
+ this.y.a = Math.min( this.y.a, rect.y.a )
+ this.y.b = Math.max( this.y.b, rect.y.b )
+ return this
+ }
+ vec2.prototype.square = function(){
+ var width = this.x.length()
+ var height = this.y.length()
+ var diff
+ if (width < height) {
+ diff = (height - width) / 2
+ this.x.a -= diff
+ this.x.b += diff
+ }
+ else {
+ diff = (width - height) / 2
+ this.y.a -= diff
+ this.y.b += diff
+ }
+ return this
+ }
+ vec2.prototype.toString = function(){
+ var sides = sidesToString(this.sides)
+ var s = "[" + this.x.toString() + " " + this.y.toString() + "] " + sides
+ return s
+ }
+ vec2.prototype.exactString = function(){
+ var sides = sidesToString(this.sides)
+ var s = "[" + this.x.exactString() + " " + this.y.exactString() + "] " + sides
+ return s
+ }
+
+ vec2.prototype.serialize = function(){
+ return { x: this.x.serialize(), y: this.y.serialize() }
+ }
+ vec2.prototype.quantize = function(n){
+ this.x.quantize(n)
+ this.y.quantize(n)
+ return this
+ }
+ vec2.prototype.split = function(r){
+ var rz = this
+ var splits = []
+ var sides = this.sides
+
+ // bisect (or trisect) two overlapping rectangles
+ var x_intervals = this.x.split( r.x, LEFT, RIGHT )
+ var y_intervals = this.y.split( r.y, FRONT, BACK )
+
+ // generate rectangular regions by crossing the two sets of vectors
+ x_intervals.forEach(function(x, i){
+ y_intervals.forEach(function(y, i){
+ var rn = new vec2(x[0], y[0])
+ rn.id = rz.id
+ rn.sides = ((x[1] | y[1]) & sides)
+ rn.focused = rz.focused
+ splits.push(rn)
+
+ // cull extra walls from overlapping regions
+ if (r.x.contains(rn.x.a) && r.x.contains(rn.x.b)) {
+ if (rz.y.a == rn.y.a && r.y.containsCenter(rn.y.a)) { // top edges
+ rn.sides &= ~ FRONT
+ }
+ if (rz.y.b == rn.y.b && r.y.containsCenter(rn.y.b)) { // bottom edges
+ rn.sides &= ~ BACK
+ }
+ }
+
+ if (r.y.contains(rn.y.a) && r.y.contains(rn.y.b)) {
+ if (rz.x.a == rn.x.a && r.x.containsCenter(rn.x.a)) { // left edges
+ rn.sides &= ~ LEFT
+ }
+
+ if (rz.x.b == rn.x.b && r.x.containsCenter(rn.x.b) ) { // right edges
+ rn.sides &= ~ RIGHT
+ }
+ }
+
+ })
+ })
+
+ return splits
+ }
+
+ if ('window' in this) {
+ window.vec2 = vec2
+ }
+ else {
+ module.exports = vec2
+ }
+
+})()
diff --git a/assets/javascripts/vendor/canvasutilities.js b/assets/javascripts/vendor/canvasutilities.js
new file mode 100644
index 0000000..011ebb0
--- /dev/null
+++ b/assets/javascripts/vendor/canvasutilities.js
@@ -0,0 +1,145 @@
+var drawArrow = function(ctx, x1, y1, x2, y2, style, which, angle, d) {
+ 'use strict';
+ // Ceason pointed to a problem when x1 or y1 were a string, and concatenation
+ // would happen instead of addition
+ if (typeof(x1) == 'string') x1 = parseInt(x1);
+ if (typeof(y1) == 'string') y1 = parseInt(y1);
+ if (typeof(x2) == 'string') x2 = parseInt(x2);
+ if (typeof(y2) == 'string') y2 = parseInt(y2);
+ style = typeof(style) != 'undefined' ? style : 3;
+ which = typeof(which) != 'undefined' ? which : 1; // end point gets arrow
+ angle = typeof(angle) != 'undefined' ? angle : Math.PI / 8;
+ d = typeof(d) != 'undefined' ? d : 10;
+ // default to using drawHead to draw the head, but if the style
+ // argument is a function, use it instead
+ var toDrawHead = typeof(style) != 'function' ? drawHead : style;
+
+ // For ends with arrow we actually want to stop before we get to the arrow
+ // so that wide lines won't put a flat end on the arrow.
+ //
+ var dist = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
+ var ratio = (dist - d / 3) / dist;
+ var tox, toy, fromx, fromy;
+ if (which & 1) {
+ tox = Math.round(x1 + (x2 - x1) * ratio);
+ toy = Math.round(y1 + (y2 - y1) * ratio);
+ } else {
+ tox = x2;
+ toy = y2;
+ }
+ if (which & 2) {
+ fromx = x1 + (x2 - x1) * (1 - ratio);
+ fromy = y1 + (y2 - y1) * (1 - ratio);
+ } else {
+ fromx = x1;
+ fromy = y1;
+ }
+
+ // Draw the shaft of the arrow
+ ctx.beginPath();
+ ctx.moveTo(fromx, fromy);
+ ctx.lineTo(tox, toy);
+ ctx.stroke();
+
+ // calculate the angle of the line
+ var lineangle = Math.atan2(y2 - y1, x2 - x1);
+ // h is the line length of a side of the arrow head
+ var h = Math.abs(d / Math.cos(angle));
+
+ if (which & 1) { // handle far end arrow head
+ var angle1 = lineangle + Math.PI + angle;
+ var topx = x2 + Math.cos(angle1) * h;
+ var topy = y2 + Math.sin(angle1) * h;
+ var angle2 = lineangle + Math.PI - angle;
+ var botx = x2 + Math.cos(angle2) * h;
+ var boty = y2 + Math.sin(angle2) * h;
+ toDrawHead(ctx, topx, topy, x2, y2, botx, boty, style);
+ }
+ if (which & 2) { // handle near end arrow head
+ var angle1 = lineangle + angle;
+ var topx = x1 + Math.cos(angle1) * h;
+ var topy = y1 + Math.sin(angle1) * h;
+ var angle2 = lineangle - angle;
+ var botx = x1 + Math.cos(angle2) * h;
+ var boty = y1 + Math.sin(angle2) * h;
+ toDrawHead(ctx, topx, topy, x1, y1, botx, boty, style);
+ }
+}
+
+var drawHead = function(ctx, x0, y0, x1, y1, x2, y2, style) {
+ 'use strict';
+ if (typeof(x0) == 'string') x0 = parseInt(x0);
+ if (typeof(y0) == 'string') y0 = parseInt(y0);
+ if (typeof(x1) == 'string') x1 = parseInt(x1);
+ if (typeof(y1) == 'string') y1 = parseInt(y1);
+ if (typeof(x2) == 'string') x2 = parseInt(x2);
+ if (typeof(y2) == 'string') y2 = parseInt(y2);
+ var radius = 3;
+ var twoPI = 2 * Math.PI;
+
+ // all cases do this.
+ ctx.save();
+ ctx.beginPath();
+ ctx.moveTo(x0, y0);
+ ctx.lineTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ switch (style) {
+ case 0:
+ // curved filled, add the bottom as an arcTo curve and fill
+ var backdist = Math.sqrt(((x2 - x0) * (x2 - x0)) + ((y2 - y0) * (y2 - y0)));
+ ctx.arcTo(x1, y1, x0, y0, .55 * backdist);
+ ctx.fill();
+ break;
+ case 1:
+ // straight filled, add the bottom as a line and fill.
+ ctx.beginPath();
+ ctx.moveTo(x0, y0);
+ ctx.lineTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.lineTo(x0, y0);
+ ctx.fill();
+ break;
+ case 2:
+ // unfilled head, just stroke.
+ ctx.stroke();
+ break;
+ case 3:
+ //filled head, add the bottom as a quadraticCurveTo curve and fill
+ var cpx = (x0 + x1 + x2) / 3;
+ var cpy = (y0 + y1 + y2) / 3;
+ ctx.quadraticCurveTo(cpx, cpy, x0, y0);
+ ctx.fill();
+ break;
+ case 4:
+ //filled head, add the bottom as a bezierCurveTo curve and fill
+ var cp1x, cp1y, cp2x, cp2y, backdist;
+ var shiftamt = 5;
+ if (x2 == x0) {
+ // Avoid a divide by zero if x2==x0
+ backdist = y2 - y0;
+ cp1x = (x1 + x0) / 2;
+ cp2x = (x1 + x0) / 2;
+ cp1y = y1 + backdist / shiftamt;
+ cp2y = y1 - backdist / shiftamt;
+ } else {
+ backdist = Math.sqrt(((x2 - x0) * (x2 - x0)) + ((y2 - y0) * (y2 - y0)));
+ var xback = (x0 + x2) / 2;
+ var yback = (y0 + y2) / 2;
+ var xmid = (xback + x1) / 2;
+ var ymid = (yback + y1) / 2;
+
+ var m = (y2 - y0) / (x2 - x0);
+ var dx = (backdist / (2 * Math.sqrt(m * m + 1))) / shiftamt;
+ var dy = m * dx;
+ cp1x = xmid - dx;
+ cp1y = ymid - dy;
+ cp2x = xmid + dx;
+ cp2y = ymid + dy;
+ }
+
+ ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x0, y0);
+ ctx.fill();
+ break;
+ }
+ ctx.restore();
+}
diff --git a/assets/javascripts/vendor/tube.js b/assets/javascripts/vendor/tube.js
new file mode 100644
index 0000000..17d3bfd
--- /dev/null
+++ b/assets/javascripts/vendor/tube.js
@@ -0,0 +1,323 @@
+var nextTick = (function(){
+ // postMessage behaves badly on IE8
+ if (window.ActiveXObject || !window.postMessage) {
+ var nextTick = function(fn) {
+ setTimeout(fn, 0);
+ }
+ } else {
+ // based on setZeroTimeout by David Baron
+ // - http://dbaron.org/log/20100309-faster-timeouts
+ var timeouts = []
+ , name = 'next-tick-zero-timeout'
+
+ window.addEventListener('message', function(e){
+ if (e.source == window && e.data == name) {
+ if (e.stopPropagation) e.stopPropagation();
+ if (timeouts.length) timeouts.shift()();
+ }
+ }, true);
+
+ var nextTick = function(fn){
+ timeouts.push(fn);
+ window.postMessage(name, '*');
+ }
+ }
+
+ return nextTick;
+})()
+
+var Uid = (function(){
+ var id = 0
+ return function(){ return id++ + "" }
+})()
+
+
+var tokenize = (function(){
+ var tokenize = function(str, splitOn){
+ return str
+ .trim()
+ .split(splitOn || tokenize.default);
+ };
+
+ tokenize.default = /\s+/g;
+
+ return tokenize;
+})()
+
+// globber("*".split(":"), "a:b:c".split(":")) => true
+// globber("*:c".split(":"), "a:b:c".split(":")) => true
+// globber("a:*".split(":"), "a:b:c".split(":")) => true
+// globber("a:*:c".split(":"), "a:b:c".split(":")) => true
+
+// based on codegolf.stackexchange.com/questions/467/implement-glob-matcher
+var globber = function(patterns, strings) {
+ // console.log("globber called with: " + patterns.join(":"), strings.join(":"))
+ var first = patterns[0],
+ rest = patterns.slice(1),
+ len = strings.length,
+ matchFound;
+
+ if(first === '*') {
+ for(var i = 0; i <= len; ++i) {
+ // console.log("* " + i + " trying " + rest.join(":") + " with " + strings.slice(i).join(":"))
+ if(globber(rest, strings.slice(i))) return true;
+ }
+ return false;
+ } else {
+ matchFound = (first === strings[0]);
+ // console.log ("literal matching " + first + " " + strings[0] + " " + !!matched)
+ }
+
+ return matchFound && ((!rest.length && !len) || globber(rest, strings.slice(1)));
+};
+
+var setproto = function(obj, proto){
+ if (obj.__proto__)
+ obj.__proto__ = proto;
+ else
+ for (var key in proto)
+ obj[key] = proto[key];
+};
+
+
+var Tube = (function(){
+ var globcache = {};
+ var Tube = function(opts){
+ opts = opts || {};
+ if (opts.queue){
+ var c = function(){
+ var args = arguments;
+ // queueOrNextTick (function(){ c.send.apply(c, args) });
+ nextTick (function(){ c.send.apply(c, args) });
+ return c;
+ };
+ } else {
+ var c = function(){
+ c.send.apply(c, arguments);
+ return c;
+ };
+ }
+
+ setproto(c, Tube.proto);
+ c.listeners = {};
+ c.globListeners = {};
+
+ return c;
+ };
+
+ Tube.total = {};
+ Tube.proto = {};
+
+ /*
+ adds fns as listeners to a channel
+
+ on("msg", fn, {opts})
+ on("msg", [fn, fn2], {opts})
+ on("msg msg2 msg3", fn, {opts})
+ on({"msg": fn, "msg2": fn2}, {opts})
+ */
+
+ Tube.proto.on = function(){
+ var chan = this;
+ if (typeof arguments[0] === "string") {
+ //if (arguments.length > 1) { // on("msg", f)
+ var msgMap = {};
+ msgMap[arguments[0]] = arguments[1];
+ var opts = arguments[2] || {};
+ } else { // on({"msg": f, ...})
+ var msgMap = arguments[0];
+ var opts = arguments[1] || {};
+ }
+
+ for (var string in msgMap){
+ var msgs = string.split(" ");
+ var fs = msgMap[string];
+ if (!Array.isArray(fs)) fs = [fs];
+
+ for(var i=0, f; f=fs[i]; i++){
+ if (!f.uid) f.uid = Uid();
+ }
+
+ for(var i=0, msg; msg=msgs[i]; i++){
+ var listeners = (msg.indexOf("*") === -1) ?
+ chan.listeners :
+ chan.globListeners;
+
+ // todo: this probably wastes a lot of memory?
+ // make a copy of the listener, add to it, and replace the listener
+ // why not just push directly?
+ // send might be iterating over it... and that will fuck up the iteration
+
+ listeners[msg] = (msg in listeners) ?
+ listeners[msg].concat(fs) :
+ fs.concat();
+ }
+ }
+
+ return chan;
+ };
+
+ /*
+ off()
+ off("a:b:c")
+ off(f)
+ off("a:b:c", f)
+ off("a:b:c d:e:f")
+ off([f, f2])
+ off({"a": f, "b": f2})
+ */
+
+ Tube.proto.off = function(){ var chan = this;
+
+ var listeners, i, msgs, msg;
+
+ // off() : delete all listeners. but replace, instead of delete
+ if (arguments.length === 0) {
+ chan.listeners = {};
+ chan.globListeners = {};
+ return chan;
+ }
+
+ // off("a:b:c d:e:f")
+ // remove all matching listeners
+ if (arguments.length === 1 && typeof arguments[0] === "string"){
+ // question... will this fuck up send if we delete in the middle of it dispatching?
+ msgs = arguments[0].split(" ");
+
+ for (i=0; msg=msgs[i]; i++){
+ delete chan.listeners[msg];
+ delete chan.globListeners[msg];
+ }
+ return chan;
+ }
+
+ // off(f) or off([f, f2])
+ // remove all matching functions
+ if (typeof arguments[0] === "function" || Array.isArray(arguments[0])) {
+ var fs = (typeof arguments[0] === "function") ?
+ [arguments[0]] :
+ arguments[0];
+ // TODO
+ return chan;
+ }
+
+ // off("a:b:c", f) or off({"a": f, "b": f2})
+ if (arguments.length > 1) { // off("msg", f)
+ var msgMap = {};
+ msgMap[arguments[0]] = arguments[1];
+ } else { // off({"msg": f, ...})
+ var msgMap = arguments[0];
+ }
+
+ for (var string in msgMap){
+ msgs = string.split(" ");
+
+ var fs = msgMap[string];
+ if (typeof fs === "function") fs = [fs];
+
+ for(var i=0; msg=msgs[i]; i++){
+ if (msg in chan.listeners)
+ listeners = chan.listeners;
+ else if (msg in chan.globListeners)
+ listeners = chan.globListeners;
+ else
+ continue;
+
+ // gotta do this carefully in case we are still iterating through the listener in send
+ // build a new array and assign it to the property, instead of mutating it.
+
+ // console.log(" length of listeners[" + msg + "]: " + listeners[msg].length)
+ // console.log(listeners[msg].join(","));
+ // console.log(fs.join(","));
+
+ listeners[msg] = listeners[msg].filter(
+ function(f){ return fs.indexOf(f) === -1 }
+ );
+
+ // console.log(" length of listeners[" + msg + "]: " + listeners[msg].length)
+
+ }
+ }
+
+ return chan;
+
+ };
+
+ /*
+ c = Tube()
+ c.on("foo", fn)
+ c("foo", "bar", [])
+
+ will call fn("bar", [], "foo")
+ */
+
+ Tube.proto.send = function(msgString /*, data... */){
+ // todo: don't do this?
+ if (!Tube.total[msgString]) Tube.total[msgString] = 0
+ Tube.total[msgString]+=1;
+
+ var listener,
+ listeners = this.listeners,
+ globListeners = this.globListeners,
+ //args = Array.prototype.splice.call(arguments, 1),
+ msgs = tokenize(msgString),
+ msg, f;
+
+ if (arguments.length) {
+ var args = Array.prototype.splice.call(arguments, 1);
+ args.push(msgString);
+
+ } else {
+ var args = [];
+ }
+
+ for (var m=0; msg=msgs[m]; m++){
+
+ var fsToRun = [];
+ var uidKeyFnValue = {};
+ var uidKeyMsgStringValue = {};
+
+ // note this will die on errors
+ // todo: implement http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/
+ // exact matches
+ if (listener = listeners[msg]) {
+ for (var i=0; f=listener[i]; i++){
+ // fsToRun.push([f, msg]);
+ uidKeyFnValue[f.uid] = f;
+ uidKeyMsgStringValue[f.uid] = msg;
+ }
+ }
+
+ // glob matches
+ var msgSplit = msg.split(":");
+
+ for (var pattern in globListeners){
+
+ if (pattern !== "*") { // * always matches
+ var patternSplit = globcache[pattern] || (globcache[pattern] = pattern.split(":"));
+ if (!globber(patternSplit, msgSplit)) continue;
+ }
+
+ listener = globListeners[pattern];
+
+ for (var i=0; f=listener[i]; i++){
+ //f.apply(window, args); // hm possibly pass the actual message to the func
+ // fsToRun.push([f, msg]);
+ uidKeyFnValue[f.uid] = f;
+ uidKeyMsgStringValue[f.uid] = msg;
+ }
+ }
+
+ var fns = [];
+ for (var f in uidKeyFnValue) fns.push(uidKeyFnValue[f]);
+
+ for (var i=0, f; f=fns[i]; i++)
+ f.apply(f, args);
+
+ }
+ return this;
+ };
+
+ return Tube;
+})()
+