--- /dev/null
+/*
+CAKE - Canvas Animation Kit Experiment
+
+Copyright (C) 2007 Ilmari Heikkinen
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+
+/**
+ Delete the first instance of obj from the array.
+
+ @param obj The object to delete
+ @return true on success, false if array contains no instances of obj
+ @type boolean
+ @addon
+ */
+Array.prototype.deleteFirst = function(obj) {
+ for (var i=0; i<this.length; i++) {
+ if (this[i] == obj) {
+ this.splice(i,1)
+ return true
+ }
+ }
+ return false
+}
+
+Array.prototype.stableSort = function(cmp) {
+ // hack to work around Chrome's qsort
+ for(var i=0; i<this.length; i++) {
+ this[i].__arrayPos = i;
+ }
+ return this.sort(Array.__stableSorter(cmp));
+}
+Array.__stableSorter = function(cmp) {
+ return (function(c1, c2) {
+ var r = cmp(c1,c2);
+ if (!r) { // hack to work around Chrome's qsort
+ return c1.__arrayPos - c2.__arrayPos
+ }
+ return r;
+ });
+}
+
+/**
+ Compares two arrays for equality. Returns true if the arrays are equal.
+ */
+Array.prototype.equals = function(array) {
+ if (!array) return false
+ if (this.length != array.length) return false
+ for (var i=0; i<this.length; i++) {
+ var a = this[i]
+ var b = array[i]
+ if (a.equals && typeof(a.equals) == 'function') {
+ if (!a.equals(b)) return false
+ } else if (a != b) {
+ return false
+ }
+ }
+ return true
+}
+
+/**
+ Rotates the first element of an array to be the last element.
+ Rotates last element to be the first element when backToFront is true.
+
+ @param {boolean} backToFront Whether to move the last element to the front or not
+ @return The last element when backToFront is false, the first element when backToFront is true
+ @addon
+ */
+Array.prototype.rotate = function(backToFront) {
+ if (backToFront) {
+ this.unshift(this.pop())
+ return this[0]
+ } else {
+ this.push(this.shift())
+ return this[this.length-1]
+ }
+}
+/**
+ Returns a random element from the array.
+
+ @return A random element
+ @addon
+ */
+Array.prototype.pick = function() {
+ return this[Math.floor(Math.random()*this.length)]
+}
+
+Array.prototype.flatten = function() {
+ var a = []
+ for (var i=0; i<this.length; i++) {
+ var e = this[i]
+ if (e.flatten) {
+ var ef = e.flatten()
+ for (var j=0; j<ef.length; j++) {
+ a[a.length] = ef[j]
+ }
+ } else {
+ a[a.length] = e
+ }
+ }
+ return a
+}
+
+Array.prototype.take = function() {
+ var a = []
+ for (var i=0; i<this.length; i++) {
+ var e = []
+ for (var j=0; j<arguments.length; j++) {
+ e[j] = this[i][arguments[j]]
+ }
+ a[i] = e
+ }
+ return a
+}
+
+if (!Array.prototype.pluck) {
+ Array.prototype.pluck = function(key) {
+ var a = []
+ for (var i=0; i<this.length; i++) {
+ a[i] = this[i][key]
+ }
+ return a
+ }
+}
+
+Array.prototype.set = function(key, value) {
+ for (var i=0; i<this.length; i++) {
+ this[i][key] = value
+ }
+}
+
+Array.prototype.allWith = function() {
+ var a = []
+ topLoop:
+ for (var i=0; i<this.length; i++) {
+ var e = this[i]
+ for (var j=0; j<arguments.length; j++) {
+ if (!this[i][arguments[j]])
+ continue topLoop
+ }
+ a[a.length] = e
+ }
+ return a
+}
+
+// some common helper methods
+
+if (!Function.prototype.bind) {
+ /**
+ Creates a function that calls this function in the scope of the given
+ object.
+
+ var obj = { x: 'obj' }
+ var f = function() { return this.x }
+ window.x = 'window'
+ f()
+ // => 'window'
+ var g = f.bind(obj)
+ g()
+ // => 'obj'
+
+ @param object Object to bind this function to
+ @return Function bound to object
+ @addon
+ */
+ Function.prototype.bind = function(object) {
+ var t = this
+ return function() {
+ return t.apply(object, arguments)
+ }
+ }
+}
+
+if (!Array.prototype.last) {
+ /**
+ Returns the last element of the array.
+
+ @return The last element of the array
+ @addon
+ */
+ Array.prototype.last = function() {
+ return this[this.length-1]
+ }
+}
+if (!Array.prototype.indexOf) {
+ /**
+ Returns the index of obj if it is in the array.
+ Returns -1 otherwise.
+
+ @param obj The object to find from the array.
+ @return The index of obj or -1 if obj isn't in the array.
+ @addon
+ */
+ Array.prototype.indexOf = function(obj) {
+ for (var i=0; i<this.length; i++)
+ if (obj == this[i]) return i
+ return -1
+ }
+}
+if (!Array.prototype.includes) {
+ /**
+ Returns true if obj is in the array.
+ Returns false if it isn't.
+
+ @param obj The object to find from the array.
+ @return True if obj is in the array, false if it isn't
+ @addon
+ */
+ Array.prototype.includes = function(obj) {
+ return (this.indexOf(obj) >= 0);
+ }
+}
+/**
+ Iterate function f over each element of the array and return an array
+ of the return values.
+
+ @param f Function to apply to each element
+ @return An array of return values from applying f on each element of the array
+ @type Array
+ @addon
+ */
+Array.prototype.map = function(f) {
+ var na = new Array(this.length)
+ if (f)
+ for (var i=0; i<this.length; i++) na[i] = f(this[i], i, this)
+ else
+ for (var i=0; i<this.length; i++) na[i] = this[i]
+ return na
+}
+Array.prototype.forEach = function(f) {
+ for (var i=0; i<this.length; i++) f(this[i], i, this)
+}
+if (!Array.prototype.reduce) {
+ Array.prototype.reduce = function(f, s) {
+ var i = 0
+ if (arguments.length == 1) {
+ s = this[0]
+ i++
+ }
+ for(; i<this.length; i++) {
+ s = f(s, this[i], i, this)
+ }
+ return s
+ }
+}
+if (!Array.prototype.find) {
+ Array.prototype.find = function(f) {
+ for(var i=0; i<this.length; i++) {
+ if (f(this[i], i, this)) return this[i]
+ }
+ }
+}
+
+if (!String.prototype.capitalize) {
+ /**
+ Returns a copy of this string with the first character uppercased.
+
+ @return Capitalized version of the string
+ @type String
+ @addon
+ */
+ String.prototype.capitalize = function() {
+ return this.replace(/^./, this.slice(0,1).toUpperCase())
+ }
+}
+
+if (!String.prototype.escape) {
+ /**
+ Returns a version of the string that can be used as a string literal.
+
+ @return Copy of string enclosed in double-quotes, with double-quotes
+ inside string escaped.
+ @type String
+ @addon
+ */
+ String.prototype.escape = function() {
+ return '"' + this.replace(/"/g, '\\"') + '"'
+ }
+}
+if (!String.prototype.splice) {
+ String.prototype.splice = function(start, count, replacement) {
+ return this.slice(0,start) + replacement + this.slice(start+count)
+ }
+}
+if (!String.prototype.strip) {
+ /**
+ Returns a copy of the string with preceding and trailing whitespace
+ removed.
+
+ @return Copy of string sans surrounding whitespace.
+ @type String
+ @addon
+ */
+ String.prototype.strip = function() {
+ return this.replace(/^\s+|\s+$/g, '')
+ }
+}
+
+if (!window['$A']) {
+ /**
+ Creates a new array from an object with #length.
+ */
+ $A = function(obj) {
+ var a = new Array(obj.length)
+ for (var i=0; i<obj.length; i++)
+ a[i] = obj[i]
+ return a
+ }
+}
+
+if (!window['$']) {
+ $ = function(id) {
+ return document.getElementById(id)
+ }
+}
+
+if (!Math.sinh) {
+ /**
+ Returns the hyperbolic sine of x.
+
+ @param x The value for x
+ @return The hyperbolic sine of x
+ @addon
+ */
+ Math.sinh = function(x) {
+ return 0.5 * (Math.exp(x) - Math.exp(-x))
+ }
+ /**
+ Returns the inverse hyperbolic sine of x.
+
+ @param x The value for x
+ @return The inverse hyperbolic sine of x
+ @addon
+ */
+ Math.asinh = function(x) {
+ return Math.log(x + Math.sqrt(x*x + 1))
+ }
+}
+if (!Math.cosh) {
+ /**
+ Returns the hyperbolic cosine of x.
+
+ @param x The value for x
+ @return The hyperbolic cosine of x
+ @addon
+ */
+ Math.cosh = function(x) {
+ return 0.5 * (Math.exp(x) + Math.exp(-x))
+ }
+ /**
+ Returns the inverse hyperbolic cosine of x.
+
+ @param x The value for x
+ @return The inverse hyperbolic cosine of x
+ @addon
+ */
+ Math.acosh = function(x) {
+ return Math.log(x + Math.sqrt(x*x - 1))
+ }
+}
+
+/**
+ Creates and configures a DOM element.
+
+ The tag of the element is given by name.
+
+ If params is a string, it is used as the innerHTML of the created element.
+ If params is a DOM element, it is appended to the created element.
+ If params is an object, it is treated as a config object and merged
+ with the created element.
+
+ If params is a string or DOM element, the third argument is treated
+ as the config object.
+
+ Special attributes of the config object:
+ * content
+ - if content is a string, it is used as the innerHTML of the
+ created element
+ - if content is an element, it is appended to the created element
+ * style
+ - the style object is merged with the created element's style
+
+ @param {String} name The tag for the created element
+ @param params The content or config for the created element
+ @param config The config for the created element if params is content
+ @return The created DOM element
+ */
+E = function(name, params, config) {
+ var el = document.createElement(name)
+ if (params) {
+ if (typeof(params) == 'string') {
+ el.innerHTML = params
+ params = config
+ } else if (params.DOCUMENT_NODE) {
+ el.appendChild(params)
+ params = config
+ }
+ if (params) {
+ if (params.style) {
+ var style = params.style
+ params = Object.clone(params)
+ delete params.style
+ Object.forceExtend(el.style, style)
+ }
+ if (params.content) {
+ if (typeof(params.content) == 'string') {
+ el.appendChild(T(params.content))
+ } else {
+ el.appendChild(params.content)
+ }
+ params = Object.clone(params)
+ delete params.content
+ }
+ Object.forceExtend(el, params)
+ }
+ }
+ return el
+}
+E.append = function(node) {
+ for(var i=1; i<arguments.length; i++) {
+ if (typeof(arguments[i]) == 'string') {
+ node.appendChild(T(arguments[i]))
+ } else {
+ node.appendChild(arguments[i])
+ }
+ }
+}
+// Safari requires each canvas to have a unique id.
+E.lastCanvasId = 0
+/**
+ Creates and returns a canvas element with width w and height h.
+
+ @param {int} w The width for the canvas
+ @param {int} h The height for the canvas
+ @param config Optional config object to pass to E()
+ @return The created