diff options
| author | Jules Laplace <jules@okfoc.us> | 2014-11-17 13:37:10 -0500 |
|---|---|---|
| committer | Jules Laplace <jules@okfoc.us> | 2014-11-17 13:37:10 -0500 |
| commit | 548ef92e8157f1ae0b594d0fd2c609438d748222 (patch) | |
| tree | bfef41275e7135eaad4e29e1e48d6fa592ab3a04 /assets/javascripts/math | |
| parent | 4f21d5b8b72aaf93581fca28e232156da23f312c (diff) | |
libraries
Diffstat (limited to 'assets/javascripts/math')
| -rw-r--r-- | assets/javascripts/math/point.js | 227 | ||||
| -rw-r--r-- | assets/javascripts/math/util.js | 256 | ||||
| -rw-r--r-- | assets/javascripts/math/vec2.js | 237 |
3 files changed, 720 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 + } + +})() |
