summaryrefslogtreecommitdiff
path: root/assets/javascripts
diff options
context:
space:
mode:
Diffstat (limited to 'assets/javascripts')
-rw-r--r--assets/javascripts/app.js166
-rw-r--r--assets/javascripts/environments/app.js30
-rw-r--r--assets/javascripts/environments/tableaux/_empty.js32
-rw-r--r--assets/javascripts/environments/tableaux/columns.js36
-rw-r--r--assets/javascripts/environments/tableaux/columns_circle.js42
-rw-r--r--assets/javascripts/environments/tableaux/columns_split.js63
-rw-r--r--assets/javascripts/map/map.js337
-rw-r--r--assets/javascripts/map/map_editor.js102
-rw-r--r--assets/javascripts/mx/extensions/mx.movements.js264
-rw-r--r--assets/javascripts/mx/extensions/mx.rotationControl.js265
-rw-r--r--assets/javascripts/mx/extensions/mx.scene.js165
-rw-r--r--assets/javascripts/mx/extensions/mx.scrubber.js191
-rw-r--r--assets/javascripts/mx/mx.js582
-rw-r--r--assets/javascripts/mx/mx.min.js1
-rw-r--r--assets/javascripts/mx/mx.minimap.js211
-rw-r--r--assets/javascripts/mx/mx.quaternion.js414
-rw-r--r--assets/javascripts/mx/primitives/mx.box.js62
-rw-r--r--assets/javascripts/mx/primitives/mx.boxDimensions.js154
-rw-r--r--assets/javascripts/mx/primitives/mx.coords.js61
-rw-r--r--assets/javascripts/mx/primitives/mx.cutout.js66
-rw-r--r--assets/javascripts/mx/primitives/mx.face.js41
-rw-r--r--assets/javascripts/mx/primitives/mx.image.js49
-rw-r--r--assets/javascripts/mx/primitives/mx.scaleBox.js140
-rw-r--r--assets/javascripts/mx/primitives/mx.tableau.js48
-rw-r--r--assets/javascripts/mx/primitives/mx.text.js34
-rw-r--r--assets/javascripts/mx/primitives/mx.texturedBox.js121
-rw-r--r--assets/javascripts/util.js165
-rw-r--r--assets/javascripts/vendor/loader.js68
-rw-r--r--assets/javascripts/vendor/tube.js323
-rw-r--r--assets/javascripts/vendor/tween.js741
30 files changed, 4974 insertions, 0 deletions
diff --git a/assets/javascripts/app.js b/assets/javascripts/app.js
new file mode 100644
index 0000000..862ea62
--- /dev/null
+++ b/assets/javascripts/app.js
@@ -0,0 +1,166 @@
+
+
+// Check if supports 3D transforms
+function has3d(){
+ var el = $('<p>')[0], $iframe = $('<iframe>'), has3d, t,
+ transforms = {
+ 'webkitTransform': '-webkit-transform',
+ 'OTransform': '-o-transform',
+ 'msTransform': '-ms-transform',
+ 'transform': 'transform'
+ };
+
+ // Add it to the body to get the computed style
+ // Sandbox it inside an iframe to avoid Android Browser quirks
+ $iframe.appendTo('body').contents().find('body').append( el );
+
+ for (t in transforms) {
+ if (el.style[t] !== undefined) {
+ el.style[t] = 'translate3d(1px,1px,1px)';
+ has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]);
+ }
+ }
+
+ $iframe.remove();
+
+ return has3d !== undefined && has3d.length > 0 && has3d !== "none";
+}
+
+
+
+// Identify browser based on useragent string
+(function( ua ) {
+ ua = ua.toLowerCase();
+ var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+ /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+ /(msie) ([\w.]+)/.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+ [];
+ var matched = {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+ browser = {};
+ if ( matched.browser ) {
+ browser[ matched.browser ] = true;
+ browser.version = matched.version;
+ }
+ // Chrome is Webkit, but Webkit is also Safari.
+ if ( browser.chrome ) {
+ browser.webkit = true;
+ } else if ( browser.webkit ) {
+ browser.safari = true;
+ }
+ $.browser = browser;
+ return browser;
+})( navigator.userAgent );
+
+var is_iphone = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i));
+var is_ipad = (navigator.userAgent.match(/iPad/i));
+var is_android = (navigator.userAgent.match(/Android/i))
+var is_mobile = is_iphone || is_ipad || is_android;
+
+if (is_mobile) {
+ window.location.href = "mobile.html"
+}
+else if ($.browser.msie || ! has3d()) {
+ window.location.href = "error.html"
+}
+
+var scene,
+ cam,
+ map;
+
+var viewHeight = window.viewHeight || 550
+
+var app = new function(){}
+app.dragging = false
+
+app.init = function () {
+
+ var mainbox,
+ coords,
+ box, size,
+ floor,
+ movements
+
+ scene = new MX.Scene().addTo('#scene')
+ scene.width = window.innerWidth
+ scene.height = window.innerHeight
+ scene.perspective = window.innerHeight
+
+ window.onresize = function () {
+ scene.width = window.innerWidth
+ scene.height = window.innerHeight
+ scene.perspective = window.innerHeight
+ scene.update()
+ }
+
+ cam = scene.camera
+ cam.y = viewHeight
+
+ if (MX.Map) map = app.map = new MX.Map()
+
+ movements = app.movements = new MX.Movements(cam, viewHeight)
+ movements.init()
+
+ function animate (t) {
+ requestAnimationFrame(animate)
+ environment.update(t)
+ window.path && path.update(t)
+ movements.update()
+ scene.update()
+ }
+
+ window.inAnimation = true
+
+ var loader = new Loader(function(){
+ $("#loader").hide()
+ window.environment && window.environment.init()
+ window.editor && window.editor.init()
+ window.path && window.path.init()
+ animate()
+ })
+
+ // loader.preloadImages([])
+ loader.ready()
+}
+
+app.position = function(obj){
+ return {
+ x: obj.x,
+ y: obj.y,
+ z: obj.z,
+ rotationX: obj.rotationX,
+ rotationY: obj.rotationY
+ }
+}
+
+var share = {
+ init: function(){
+ share.bind()
+ },
+ bind: function(){
+ $("#facebook").click(share.facebook)
+ $("#twitter").click(share.twitter)
+ },
+ url: "http://vvalls.com/",
+ facebook_msg: "",
+ twitter_msg: "",
+ openLink: function (url) {
+ window.open(url, "_blank");
+ },
+ facebook: function () {
+ var url = "https://www.facebook.com/share.php?u=" + encodeURIComponent(share.url) + "&t=" + encodeURIComponent(share.facebook_msg);
+ share.openLink(url);
+ return false;
+ },
+ twitter: function () {
+ var url = "https://twitter.com/home?status=" + encodeURIComponent(share.url + " " + share.twitter_msg);
+ share.openLink(url);
+ return false;
+ }
+}
+
+document.addEventListener('DOMContentLoaded', app.init)
diff --git a/assets/javascripts/environments/app.js b/assets/javascripts/environments/app.js
new file mode 100644
index 0000000..27a5222
--- /dev/null
+++ b/assets/javascripts/environments/app.js
@@ -0,0 +1,30 @@
+var scrubber, fish, floor
+
+viewHeight = 400
+
+var environment = new function(){}
+environment.init = function(){
+
+ scene.camera.move({
+ "x": 0,
+ "y": 0,
+ "z": 0,
+ "rotationX": 0.085,
+ "rotationY": 0.025
+ })
+ map && map.zoom(3.10) && map.recenter()
+
+ //
+ // intro floor, models, etc
+
+}
+
+
+environment.update = function(t){
+
+ // add continuous animations and stuff here
+
+ map && map.update()
+
+}
+
diff --git a/assets/javascripts/environments/tableaux/_empty.js b/assets/javascripts/environments/tableaux/_empty.js
new file mode 100644
index 0000000..b69fa78
--- /dev/null
+++ b/assets/javascripts/environments/tableaux/_empty.js
@@ -0,0 +1,32 @@
+/*
+
+MX.Tableaux.Foo = MX.Tableau.extend({
+
+ init: function (opt) {
+
+ this.opt = opt = defaults(opt, {
+ width: 100,
+ height: 100,
+ depth: 100,
+ x: 0,
+ y: 0,
+ z: 0,
+ rotationY: 0,
+ rotationX: 0,
+ scale: 1,
+ })
+
+ },
+
+ animate: function() {
+ },
+
+ show: function(){
+ },
+
+ hide: function(){
+ },
+
+})
+
+*/
diff --git a/assets/javascripts/environments/tableaux/columns.js b/assets/javascripts/environments/tableaux/columns.js
new file mode 100644
index 0000000..e961315
--- /dev/null
+++ b/assets/javascripts/environments/tableaux/columns.js
@@ -0,0 +1,36 @@
+MX.Tableaux.Columns = MX.Tableau.extend({
+
+ init: function (opt) {
+
+ this.opt = opt = defaults(opt, {
+ width: 10,
+ height: 10,
+ depth: 10,
+ x: 0,
+ y: 0,
+ z: 0,
+ rotationY: 0,
+ rotationX: 0,
+ scale: 1,
+ count: 3,
+ spacingX: 0,
+ spacingZ: 100,
+ })
+
+ for (var i = 0; i < opt.count; i++) {
+ var scalebox = new MX.ScaleBox({
+ "width": opt.width,
+ "height": opt.height,
+ "depth": opt.depth,
+ "x": opt.x + opt.spacingX * i,
+ "y": opt.y,
+ "z": opt.z + opt.spacingZ * i,
+ "color": opt.color,
+ "sides": "top bottom left right front back"
+ });
+ scene.add(scalebox)
+ }
+
+ }
+
+})
diff --git a/assets/javascripts/environments/tableaux/columns_circle.js b/assets/javascripts/environments/tableaux/columns_circle.js
new file mode 100644
index 0000000..1816e51
--- /dev/null
+++ b/assets/javascripts/environments/tableaux/columns_circle.js
@@ -0,0 +1,42 @@
+MX.Tableaux.ColumnsCircle = MX.Tableau.extend({
+
+ init: function (opt) {
+
+ this.opt = opt = defaults(opt, {
+ width: 10,
+ height: 10,
+ depth: 10,
+ radius: 10,
+ theta: 0,
+ skip: 0,
+ x: 0,
+ y: 0,
+ z: 0,
+ rotationY: 0,
+ rotationX: 0,
+ scale: 1,
+ count: 3,
+ })
+
+ var scalebox, theta
+ var radius = opt.radius
+
+ for (var i = opt.skip; i < opt.count; i++) {
+ theta = i/opt.count * TWO_PI + opt.theta
+ scalebox = new MX.ScaleBox({
+ "width": opt.width,
+ "height": opt.height,
+ "depth": opt.depth,
+ "x": opt.x + sin(theta) * radius,
+ "y": opt.y,
+ "z": opt.z + cos(theta) * radius,
+ "rotationY": PI - theta,
+ "color": opt.color,
+ "sides": "top bottom left right front back"
+ });
+ scene.add(scalebox)
+ }
+
+ }
+
+})
diff --git a/assets/javascripts/environments/tableaux/columns_split.js b/assets/javascripts/environments/tableaux/columns_split.js
new file mode 100644
index 0000000..b9981d6
--- /dev/null
+++ b/assets/javascripts/environments/tableaux/columns_split.js
@@ -0,0 +1,63 @@
+MX.Tableaux.ColumnsSplit = MX.Tableau.extend({
+
+ init: function (opt) {
+
+ this.opt = opt = defaults(opt, {
+ width: 100,
+ height: 100,
+ depth: 100,
+ norm: 0.5,
+ gap: 5,
+ x: 0,
+ y: 0,
+ z: 0,
+ rotationY: 0,
+ scale: 1,
+ count: 1,
+ })
+
+ opt.colorBottom = opt.colorBottom || opt.color
+
+ if ( ! (opt.norm instanceof Array) ) {
+ opt.norm = [ opt.norm, opt.norm ]
+ }
+
+ var norm, spacingX, spacingZ, scalebox
+
+ for (var i = 0; i < opt.count; i++) {
+
+ norm = lerp( i/(opt.count-1), opt.norm[0], opt.norm[1] )
+
+ spacingX = opt.spacingX * i
+ spacingZ = opt.spacingZ * i
+
+ scalebox = new MX.ScaleBox({
+ "width": opt.width,
+ "height": opt.height * norm,
+ "depth": opt.depth,
+ "x": opt.x + spacingX,
+ "y": opt.y + opt.gap,
+ "z": opt.z + spacingZ,
+ "color": opt.color,
+ "sides": "top bottom left right front back",
+ "rotationY": opt.rotationY,
+ });
+ scene.add(scalebox)
+
+ scalebox = new MX.ScaleBox({
+ "width": opt.width,
+ "height": opt.height * (1 - norm),
+ "depth": opt.depth,
+ "x": opt.x + spacingX,
+ "y": opt.y - opt.height * (1 - norm) - opt.gap,
+ "z": opt.z + spacingZ,
+ "color": opt.colorBottom,
+ "sides": "top bottom left right front back",
+ "rotationY": opt.rotationY,
+ });
+ scene.add(scalebox)
+ }
+
+ }
+
+})
diff --git a/assets/javascripts/map/map.js b/assets/javascripts/map/map.js
new file mode 100644
index 0000000..a90963f
--- /dev/null
+++ b/assets/javascripts/map/map.js
@@ -0,0 +1,337 @@
+
+MX.Map = function(){
+ var base = this;
+
+ var parent = document.querySelector("#map")
+ var canvas = document.createElement("canvas")
+ var ctx = canvas.getContext("2d")
+ var w, h
+
+ var visible = parent.style.display == "block"
+ if (visible) resize()
+
+ var center = {x:0,y:0}
+ var gridSpace;
+ var zoom = 3.0
+
+ var gridStroke = '#ddd'
+ var boxFill = '#fff'
+ var boxStroke = '#000'
+ var playerColor = '#888'
+
+ var xmin, xmax, ymin, ymax, xpos, ypos, scale, side;
+
+ var tube = base.tube = new Tube ()
+
+ this.zoom = function(n){ if (n) zoom = n; return zoom }
+ this.recenter = function(){ center.x = cam.x; center.y = cam.z }
+
+ function resize(){
+ var rect = parent.getBoundingClientRect()
+ w = canvas.width = ~~rect.width
+ h = canvas.height = ~~rect.height
+ }
+
+ this.on = function(){
+ base.tube.on.apply(base.tube, arguments)
+ }
+ this.off = function(){
+ base.tube.off.apply(base.tube, arguments)
+ }
+
+ this.update = function(){
+ if (! visible) return;
+ this.draw()
+ }
+
+ this.bounds = function(){
+ gridSpace = pow(10, ~~(zoom-0.25) + 0.25)
+ side = Math.pow(10, zoom+1)
+ scale = w / side
+ xpos = center.x // -cam.x
+ ypos = center.y // cam.z
+
+ xmin = side/-2 - xpos
+ xmax = side/2 - xpos
+ ymin = side/-2 - ypos
+ ymax = side/2 - ypos
+ }
+
+ this.draw = function(){
+ ctx.clearRect(0,0,w,h)
+
+ ctx.fillStyle = "#fff"
+ ctx.fillRect(0,0,w,h)
+ this.bounds()
+ this.grid()
+ this.boxes()
+ this.player()
+ }
+
+ this.grid = function(){
+ ctx.strokeStyle = gridStroke
+ ctx.lineWidth = 1
+ ctx.fillStyle = "transparent"
+
+ var x0 = norm(0, xmin, xmax)
+ var y0 = norm(0, ymin, ymax)
+
+ var xg = norm(gridSpace, xmin, xmax)
+ var yg = norm(gridSpace, ymin, ymax)
+
+ var xgw = (xg-x0)
+
+ var xmod = mod(x0, xgw)
+ var ymod = mod(y0, xgw)
+
+ var xend = 1 + xgw
+ var yend = h/w + xgw
+
+ var xline, yline;
+ for (var x = -xmod; x < xend; x += xgw) {
+ xline = x * w
+ line(xline, 0, xline, h)
+ }
+ for (var y = -ymod; y < yend; y += xgw) {
+ yline = y * w
+ line(0, yline, w, yline)
+ }
+
+ function line(x0,y0,x1,y1) {
+ ctx.beginPath()
+ ctx.moveTo(x0, y0)
+ ctx.lineTo(x1, y1)
+ ctx.stroke()
+ }
+ }
+ this.player = function(){
+ ctx.save()
+
+ ctx.translate(~~(w/2),~~(h/2));
+ ctx.lineWidth = 0.5
+ var tx = ((-xpos) * scale),
+ ty = ((-ypos) * scale);
+ ctx.translate(tx, ty)
+
+ var obj_scale = (1) * scale
+
+ var tx = ~~((cam.x) * obj_scale),
+ ty = ~~((cam.z) * obj_scale);
+ ctx.translate(-tx, ty)
+ ctx.rotate(-cam.rotationY)
+
+ var radius = 5
+
+ ctx.fillStyle = playerColor;
+
+ ctx.beginPath();
+ ctx.arc(0, 0, radius, 0, 2*Math.PI, false);
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.moveTo(0,0)
+ ctx.lineTo(-radius,0)
+ ctx.lineTo(0,radius*3)
+ ctx.lineTo(radius,0)
+ ctx.moveTo(0,0)
+ ctx.fill()
+
+ ctx.fillStyle = "transparent"
+ ctx.restore()
+ }
+
+ this.boxes = function(){
+
+ ctx.save()
+ ctx.translate(~~(w/2),~~(h/2));
+ ctx.lineWidth = 0.5
+ var tx = ((-xpos) * scale),
+ ty = ((-ypos) * scale);
+ ctx.translate(tx, ty)
+
+ scene.inner.children.forEach(function(obj){
+ if (obj.type == "BoxDimensions" || obj.type == "ScaleBox") {
+
+ ctx.save()
+ ctx.fillStyle = obj.color
+ ctx.strokeStyle = "#222"
+
+ var obj_scale = (obj.scale || 1) * scale
+
+ var tx = ~~((obj.x) * scale),
+ ty = ~~((obj.z) * scale);
+ ctx.translate(-tx, ty)
+ ctx.rotate(-obj.rotationY)
+
+ var ww = ~~(obj.width/2 * obj_scale)
+ var hh = ~~(obj.depth/2 * obj_scale)
+ ctx.beginPath();
+ ctx.moveTo(ww, hh)
+
+ ctx.lineTo(ww, -hh)
+ ctx.lineTo(-ww, -hh)
+ ctx.lineTo(-ww, hh)
+ ctx.closePath()
+ ctx.fill()
+ ctx.stroke()
+ ctx.restore()
+ }
+ if (obj.type == "Image" || obj.type == "Cutout") {
+ ctx.save()
+ ctx.strokeStyle = "#444"
+
+ var obj_scale = (obj.scale || 1) * scale
+
+ var tx = ~~((obj.x) * scale),
+ ty = ~~((obj.z) * scale);
+ ctx.translate(-tx, ty)
+ ctx.rotate(-obj.rotationY)
+
+ var ww = ~~(obj.width/2 * obj_scale)
+ ctx.beginPath();
+ ctx.moveTo(ww, 0)
+ ctx.lineTo(-ww, 0)
+ ctx.closePath()
+ ctx.stroke()
+ ctx.restore()
+ }
+ })
+ ctx.restore()
+ }
+
+
+ var dragging = false, mx = 0, my = 0, mdx = 0, mdy = 0, cx, cy,
+ creating = false, moving = false;
+
+ function positionFromMouse(e) {
+ var rect = canvas.getBoundingClientRect()
+ cx = center.x
+ cy = center.y
+ mx = e.pageX - rect.left
+ my = e.pageY - rect.top
+ mdx = (mx - w/2) / scale + cx
+ mdy = (my - h/2) / scale + cy
+ }
+
+ canvas.addEventListener("mousedown", function(e){
+ e.stopPropagation()
+ dragging = true
+
+ positionFromMouse(e)
+
+ if (e.shiftKey) {
+ creating = true
+ }
+
+ hud.update( mdx, mdy )
+
+ base.tube("mousedown", e, -mdx, mdy)
+ })
+ document.addEventListener("mousemove", function(e){
+ if (dragging) {
+ e.stopPropagation()
+
+ var rect = canvas.getBoundingClientRect()
+ var mnx = e.pageX - rect.left
+ var mny = e.pageY - rect.top
+
+ mdx = (mnx - mx) / scale
+ mdy = (mny - my) / scale
+
+ if (creating) {
+ hud.update( mdx, mdy )
+ }
+ else if (moving) {
+ cam.x = cx - mdx
+ cam.z = cy + mdy
+ }
+ else {
+ center.x = cx - mdx
+ center.y = cy - mdy
+ hud.update(center.x, center.y)
+ }
+
+ base.tube("mousedrag", e, -mdx, mdy)
+ }
+ else {
+ positionFromMouse(e)
+ hud.update( mdx, mdy )
+ base.tube("mousemove", e, -mdx, mdy)
+ }
+ })
+ document.addEventListener("mouseup", function(e){
+ creating = dragging = moving = false;
+ base.tube("mouseup", e)
+ })
+
+ canvas.addEventListener("contextmenu", function(e){
+ e.preventDefault()
+ e.stopPropagation()
+ dragging = true
+
+ positionFromMouse(e)
+
+ moving = true
+ cx = cam.x = -mdx
+ cy = cam.z = mdy
+
+ hud.update( mdx, mdy )
+
+ base.tube("contextmenu", e)
+ })
+
+ canvas.addEventListener( 'mousewheel', onDocumentMouseWheel, false );
+ canvas.addEventListener( 'DOMMouseScroll', onDocumentMouseWheel, false);
+ function onDocumentMouseWheel (e) {
+ e.preventDefault()
+ e.stopPropagation()
+ var delta = 0
+
+ // WebKit
+ if ( event.wheelDeltaY ) {
+ delta = - event.wheelDeltaY * 0.0003;
+ }
+ // Opera / Explorer 9
+ else if ( event.wheelDelta ) {
+ delta = - event.wheelDelta * 0.0003;
+ }
+ // Firefox
+ else if ( event.detail ) {
+ delta = event.detail * 0.01;
+ }
+
+ if (! e.shiftKey) {
+ zoom += delta
+ }
+
+ positionFromMouse(e)
+ base.tube("mousewheel", e, mdx, mdy, delta)
+
+ map.update()
+ }
+
+ window.addEventListener('resize', resize)
+
+ this.toggle = function(){
+ (visible = ! visible) ? base.show() : base.hide()
+ }
+ this.show = function(){
+ parent.style.display = "block"
+ resize()
+ }
+ this.hide = function(){
+ parent.style.display = "none"
+ }
+
+ var hud = new function(){
+ var el = document.querySelector("#map .hud")
+ this.update = function(){
+ el.innerHTML = Array.prototype.slice.call(arguments,0).map(function(s){ return typeof s == "number" ? Math.round(s) : s }).join(" ")
+ }
+ }
+ hud.update()
+
+ this.update()
+ document.querySelector("#map .el").appendChild(canvas)
+}
+
diff --git a/assets/javascripts/map/map_editor.js b/assets/javascripts/map/map_editor.js
new file mode 100644
index 0000000..754172b
--- /dev/null
+++ b/assets/javascripts/map/map_editor.js
@@ -0,0 +1,102 @@
+var editor = new function (){
+
+ var base = this;
+
+ var cube = null
+ var cx, cy
+
+ base.init = function(){
+ map.on("mousedown", base.mousedown)
+ map.on("mousemove", base.mousemove)
+ map.on("mousedrag", base.mousedrag)
+ map.on("mouseup", base.mouseup)
+ map.on("mousewheel", base.mousewheel)
+ document.getElementById("export").addEventListener("keydown", base.stopPropagation)
+ document.getElementById("export").addEventListener("mousedown", base.stopPropagation)
+ document.getElementById("export").addEventListener("mousemove", base.stopPropagation)
+ document.getElementById("export").addEventListener("mouseup", base.stopPropagation)
+ window.addEventListener('keydown', base.keydown)
+ }
+
+ base.mousedown = function(e,x,y) {
+ if (! e.shiftKey) return
+ cx = x
+ cy = y
+ cube = new MX.BoxDimensions({
+ x: cx,
+ y: -10,
+ z: cy,
+ width: 1,
+ height: 1,
+ depth: 1,
+ borderWidth: 1,
+ borderColor: "#000",
+ color: "#fff"
+ })
+ cube.persisted = false
+ scene.add( cube );
+ }
+
+ base.mousemove = function(e,x,y) {
+ }
+
+ base.mousedrag = function(e,dx,dy) {
+ if (! cube) return
+ cube.x = cx + dx/2
+ cube.z = cy + dy/2
+ cube.setWidth( abs(dx) )
+ cube.setDepth( abs(dy) )
+ cube.setHeight( max( cube.width, cube.depth ) )
+ cube.update()
+ }
+
+ base.mouseup = function(e){
+ cube = null
+ }
+
+ base.mousewheel = function(e,x,z,delta){
+ if (! e.shiftKey) return;
+
+ scene.inner.children.some(function(s){
+ if (s.contains(-x, null, z)) {
+ s.y += delta * 1000
+ s.persisted = false
+ console.log(s.id)
+ return true
+ }
+ return false
+ })
+
+ }
+
+ base.stopPropagation = function(e){ e.stopPropagation() }
+
+ base.exportObjects = function(){
+ var s = scene.inner.children
+ .filter(function(s){ return ! s.persisted })
+ .map(function(s){ return s.toString() })
+ .join("\n\n")
+ document.getElementById("export").value = s
+ }
+
+ base.exportCamera = function(){
+ var s = scene.camera.toString()
+ document.getElementById("export").value = s
+ console.log(s)
+ }
+
+ base.keydown = function(e){
+ switch (e.keyCode) {
+
+ case 67: // c
+ base.exportCamera()
+ break;
+
+ case 86: // v
+ base.exportObjects()
+ break;
+
+ }
+ }
+
+}
diff --git a/assets/javascripts/mx/extensions/mx.movements.js b/assets/javascripts/mx/extensions/mx.movements.js
new file mode 100644
index 0000000..4e964f9
--- /dev/null
+++ b/assets/javascripts/mx/extensions/mx.movements.js
@@ -0,0 +1,264 @@
+
+
+MX.Movements = function (cam, viewHeight) {
+
+ var moveForward,
+ moveLeft,
+ moveBackward,
+ moveRight,
+ moveUp,
+ moveDown,
+ turnLeft,
+ turnRight,
+ turnUp,
+ turnDown,
+ jumping = false,
+ creeping = false,
+ locked = false,
+ gravity = false
+
+ var v = 28,
+ vr = Math.PI * 0.015
+ jumpV = 30,
+ vx = vy = vz = 0,
+ creepFactor = 0.1
+
+ var DEFAULT_SCALE = scale = 1.0
+
+ return {
+
+ init: function () {
+
+ document.addEventListener('keydown', function (e) {
+ // console.log(e.keyCode)
+ if (locked) return;
+ switch ( e.keyCode ) {
+
+ case 16: // shift
+ creeping = true
+ break
+
+ case 38: // up
+ case 87: // w
+ moveForward = true
+ break
+
+ case 37: // left
+ case 65: // a
+ moveLeft = true
+ break
+
+ case 40: // down
+ case 83: // s
+ moveBackward = true
+ break
+
+ case 39: // right
+ case 68: // d
+ moveRight = true
+ break
+
+ case 81: // q
+ turnLeft = true
+ break
+
+ case 69: // e
+ turnRight = true
+ break
+
+ case 82: // r
+ turnUp = true
+ break
+
+ case 70: // f
+ turnDown = true
+ break
+
+ case 32: // space
+ if (gravity) {
+ jumping = true
+
+ vy = abs(vy) + jumpV * scale
+
+ if (e.shiftKey) {
+ vy *= -1
+ }
+ }
+ else {
+ if (e.shiftKey) {
+ moveDown = true
+ }
+ else {
+ moveUp = true
+ }
+ }
+ break
+
+ case 27: // esc
+ map.toggle()
+ break
+ }
+ })
+
+ document.addEventListener('keyup', function (e) {
+ if (locked) return;
+ switch ( e.keyCode ) {
+
+ case 16: // shift
+ creeping = false
+ break
+
+ case 38: // up
+ case 87: // w
+ moveForward = false
+ break
+
+ case 37: // left
+ case 65: // a
+ moveLeft = false
+ break
+
+ case 40: // down
+ case 83: // s
+ moveBackward = false
+ break
+
+ case 39: // right
+ case 68: // d
+ moveRight = false
+ break
+
+ case 81: // q
+ turnLeft = false
+ break
+
+ case 69: // e
+ turnRight = false
+ break
+
+ case 82: // r
+ turnUp = false
+ break
+
+ case 70: // f
+ turnDown = false
+ break
+
+ case 32: // space
+ moveUp = moveDown = false
+ break
+
+ case 48: // 0
+ cam.rotationX = 0
+ cam.rotationY = 0
+ cam.x = 0
+ cam.y = viewHeight
+ cam.z = 0
+ break
+ }
+ })
+
+ var mouseX, mouseY, dx, dy, rotX, rotY, dragging = false
+ document.addEventListener('mousedown', function (e) {
+ if (locked) return;
+ mouseX = e.pageX
+ mouseY = e.pageY
+ rotX = cam.rotationX
+ rotY = cam.rotationY
+ dragging = true
+ })
+
+ document.addEventListener('mousemove', function (e) {
+ if (locked || ! dragging || app.dragging) return
+ var dx = (e.pageX - mouseX) / window.innerWidth * Math.PI/3
+ var dy = (e.pageY - mouseY) / window.innerHeight * Math.PI/3
+ cam.rotationY = rotY + dx
+ cam.rotationX = rotX - dy
+ })
+
+ document.addEventListener('mouseup', function (e) {
+ app.dragging = dragging = false
+ })
+
+ window.addEventListener('blur', reset)
+ window.addEventListener('focus', reset)
+ function reset(){
+ moveForward = moveLeft = moveBackward = moveRight = moveUp = moveDown = turnLeft = turnRight = jumping = dragging = creeping = false
+ }
+
+ },
+
+ update: function () {
+
+ if (locked) return;
+
+ var ry = cam.rotationY
+ var s = creeping ? scale * creepFactor : scale
+ var vrrrr = creeping ? vr * creepFactor * 5 : vr
+
+ if (moveForward || moveBackward || moveRight || moveLeft || moveUp || moveDown || turnLeft || turnRight || turnUp || turnDown) {
+
+ vx = vy = vz = 0
+
+ if (moveForward) {
+ vx += v * Math.cos(ry + Math.PI / 2) * s
+ vz += v * Math.sin(ry + Math.PI / 2) * s
+ }
+ if (moveBackward) {
+ vx -= v * Math.cos(ry + Math.PI / 2) * s
+ vz -= v * Math.sin(ry + Math.PI / 2) * s
+ }
+ if (moveLeft) {
+ vx -= v * Math.cos(ry) * s
+ vz -= v * Math.sin(ry) * s
+ }
+ if (moveRight) {
+ vx += v * Math.cos(ry) * s
+ vz += v * Math.sin(ry) * s
+ }
+ if (moveUp) {
+ vy += v * scale
+ }
+ if (moveDown) {
+ vy -= v * scale
+ }
+
+ if (turnUp) {
+ cam.rotationX -= vrrrr
+ }
+ if (turnDown) {
+ cam.rotationX += vrrrr
+ }
+ if (turnLeft) {
+ cam.rotationY += vrrrr
+ }
+ if (turnRight) {
+ cam.rotationY -= vrrrr
+ }
+
+ cam.x += vx
+ cam.y += vy
+ cam.z += vz
+
+ }
+
+ if (gravity) {
+ vy -= 1 * scale
+
+ cam.y += vy
+
+ if (cam.y <= viewHeight * scale) {
+ cam.y = viewHeight * scale
+ vy = 0
+ jumping = false
+ }
+ }
+
+ },
+
+ lock: function(){ locked = true },
+ unlock: function(){ locked = false },
+ scale: function(n){ if (n) scale = n; return scale },
+ resetScale: function(n){ scale = DEFAULT_SCALE }
+ }
+}
diff --git a/assets/javascripts/mx/extensions/mx.rotationControl.js b/assets/javascripts/mx/extensions/mx.rotationControl.js
new file mode 100644
index 0000000..9adb627
--- /dev/null
+++ b/assets/javascripts/mx/extensions/mx.rotationControl.js
@@ -0,0 +1,265 @@
+// Usage:
+//
+// var control = new MX.RotationControl()
+// control.init( object{MX.Object3D} [, listener{HTMLElement}] )
+//
+// In animation loop:
+//
+// control.update()
+//
+// The above code will register handler functions on `listener`
+// and will be updating `object`s rotationX and rotationY
+// If no `listener` is provided, will default to `object`s el.
+
+MX.RotationControl = function () {
+
+ var object,
+ locked = false
+
+ var down = false,
+ active = false,
+ lastX,
+ lastY
+
+ var pointerLockPrefix =
+ 'pointerLockElement' in document ? '' :
+ 'mozPointerLockElement' in document ? 'moz' :
+ 'webkitPointerLockElement' in document ? 'webkit' : null,
+ hasPointerLock = !(pointerLockPrefix === null)
+ pointerLockEnabled = false
+
+ var pub = {
+
+ sensitivity : .5,
+ ease : 10,
+ drag : true,
+
+ inverseX : false,
+ inverseY : false,
+
+ disableX : false,
+ disableY : false,
+
+ rotationX : 0,
+ rotationY : 0,
+
+ upperBoundX : undefined,
+ lowerBoundX : undefined,
+
+ upperBoundY : undefined,
+ lowerBoundY : undefined,
+
+ usePreset: function (name) {
+ var ops = presets[name]
+ if (ops) {
+ if (currentPreset && presets[currentPreset].teardown) {
+ presets[currentPreset].teardown()
+ }
+ for (var op in ops) {
+ if (op !== 'setup' && op !== 'teardown') {
+ pub[op] = ops[op]
+ }
+ }
+ if (op.setup) ops.setup()
+ }
+ }
+ }
+
+ var currentPreset
+ var presets = {
+ firstPerson: {
+ drag: false,
+ ease: 2,
+ sensitivity: .18,
+ inverseX: true,
+ inverseY: true,
+ upperBoundX: MX.rotationUnit === 'deg' ? 90 : Math.PI / 2,
+ lowerBoundX: MX.rotationUnit === 'deg' ? -90 : -Math.PI / 2
+ },
+ skybox: {
+ sensitivity: .18,
+ inverseX: true,
+ inverseY: true,
+ upperBoundX: MX.rotationUnit === 'deg' ? 90 : Math.PI / 2,
+ lowerBoundX: MX.rotationUnit === 'deg' ? -90 : -Math.PI / 2
+ }
+ }
+
+ function init (obj, lis) {
+ if (active) return
+
+ object = obj
+ pub.rotationX = object.rotationX
+ pub.rotationY = object.rotationY
+
+ if (lis instanceof HTMLElement) {
+ listener = lis
+ } else if (lis instanceof MX.Object3D) {
+ listener = lis.el
+ } else {
+ listener = window.document
+ }
+
+ listener.addEventListener('mousedown', onDown)
+ listener.addEventListener('mousemove', onMove)
+ listener.addEventListener('mouseup', onUp)
+ listener.addEventListener('touchstart', onDown)
+ listener.addEventListener('touchmove', onMove)
+ listener.addEventListener('touchend', onUp)
+
+ active = true
+ }
+
+ function changeObject (obj) {
+ object = obj
+ pub.rotationX = object.rotationX
+ pub.rotationY = object.rotationY
+ }
+
+ function changeListener (lis) {
+ remove()
+ active = false
+ init(object, lis)
+ if (pointerLockEnabled) {
+ initPointerLock()
+ }
+ }
+
+ function remove () {
+ if (!active) return
+ listener.removeEventListener('mousedown', onDown)
+ listener.removeEventListener('mousemove', onMove)
+ listener.removeEventListener('mouseup', onUp)
+ listener.removeEventListener('touchstart', onDown)
+ listener.removeEventListener('touchmove', onMove)
+ listener.removeEventListener('touchend', onUp)
+
+ if (hasPointerLock) {
+ document.removeEventListener(pointerLockPrefix + 'pointerlockchange', onPointerLockChange)
+ document.removeEventListener('mousemove', onPointerLockMove)
+ document.body[pointerLockPrefix + (pointerLockPrefix ? 'E' : 'e') + 'xitPointerLock']()
+ }
+ active = false
+ }
+
+ function onDown (e) {
+ e = normalizeEvent(e)
+ if (!e) return
+ down = true
+ lastX = e.pageX
+ lastY = e.pageY
+ }
+
+ function onMove (e) {
+ if (e.type = 'touchmove') {
+ e.preventDefault()
+ }
+ if (pub.drag && !down) return
+ e = normalizeEvent(e)
+ if (!e) return
+ lastX = lastX || e.pageX
+ lastY = lastY || e.pageY
+ var dx = e.pageX - lastX,
+ dy = e.pageY - lastY
+ lastX = e.pageX
+ lastY = e.pageY
+ updateTarget(dx, dy)
+ }
+
+ function onUp () {
+ down = false
+ }
+
+ function initPointerLock () {
+
+ if (pointerLockEnabled) return
+
+ document.addEventListener(pointerLockPrefix + 'pointerlockchange', onPointerLockChange)
+ document.addEventListener('mousemove', onPointerLockMove)
+
+ document.body[pointerLockPrefix + (pointerLockPrefix ? 'R' : 'r') + 'equestPointerLock']()
+ }
+
+ function onPointerLockChange () {
+ var el = document.body
+ if (document[pointerLockPrefix + (pointerLockPrefix ? 'P' : 'p') + 'ointerLockElement'] === el) {
+ pointerLockEnabled = true
+ } else {
+ pointerLockEnabled = false
+ }
+ }
+
+ function onPointerLockMove (e) {
+ if (!pointerLockEnabled) return
+ var dx = e[pointerLockPrefix + (pointerLockPrefix ? 'M' : 'm') + 'ovementX'],
+ dy = e[pointerLockPrefix + (pointerLockPrefix ? 'M' : 'm') + 'ovementY']
+ updateTarget(dx, dy)
+ }
+
+ function normalizeEvent (e) {
+ if (e.touches) {
+ return e.touches.length > 1 ? false : e.touches[0]
+ } else {
+ return e
+ }
+ }
+
+ function updateTarget (dx, dy) {
+ if (pub.inverseX) dx = -dx
+ if (pub.inverseY) dy = -dy
+ if (MX.rotationUnit !== 'deg') {
+ dx = MX.toRad(dx)
+ dy = MX.toRad(dy)
+ }
+
+ if (!pub.disableX) {
+ pub.rotationX -= dy * pub.sensitivity
+ if (pub.upperBoundX) pub.rotationX = Math.min(pub.rotationX, pub.upperBoundX)
+ if (pub.lowerBoundX) pub.rotationX = Math.max(pub.rotationX, pub.lowerBoundX)
+ }
+
+ if (!pub.disableY) {
+ pub.rotationY += dx * pub.sensitivity
+ if (pub.upperBoundY) pub.rotationY = Math.min(pub.rotationY, pub.upperBoundY)
+ if (pub.lowerBoundY) pub.rotationY = Math.max(pub.rotationY, pub.lowerBoundY)
+ }
+ }
+
+ function update () {
+ if (!object || locked) return
+ var dx = pub.rotationX - object.rotationX,
+ dy = pub.rotationY - object.rotationY
+ if (Math.abs(dx) < 0.0001) {
+ object.rotationX = pub.rotationX
+ } else {
+ object.rotationX += dx / pub.ease
+ }
+ if (Math.abs(dy) < 0.0001) {
+ object.rotationY = pub.rotationY
+ } else {
+ object.rotationY += dy / pub.ease
+ }
+ }
+
+ function lock () {
+ locked = true
+ }
+
+ function unlock () {
+ pub.rotationX = object.rotationX
+ pub.rotationY = object.rotationY
+ locked = false
+ }
+
+ pub.init = init
+ pub.remove = remove
+ pub.update = update
+ pub.lock = lock
+ pub.unlock = unlock
+ pub.initPointerLock = initPointerLock
+ pub.changeObject = changeObject
+ pub.changeListener = changeListener
+
+ return pub
+
+} \ No newline at end of file
diff --git a/assets/javascripts/mx/extensions/mx.scene.js b/assets/javascripts/mx/extensions/mx.scene.js
new file mode 100644
index 0000000..8f11fb0
--- /dev/null
+++ b/assets/javascripts/mx/extensions/mx.scene.js
@@ -0,0 +1,165 @@
+// NOTE
+//
+// This is not a fully functional 3d scene as you might expect.
+// The camera can only do pitch (rotationX) and yaw (rotationY), but no roll (rotationZ)
+// because I haven't implemented alternative euler orders or quaternions.
+//
+// For serious 3D scenes with more functionalities you should use
+// THREE.js with CSS3D Renderer.
+
+MX.Camera = MX.Object3D.extend({
+
+ init: function(){
+ this.el = null
+ this.type = "Camera"
+ },
+
+ move: function(s){
+ for (var i in s) {
+ this[i] = s[i]
+ }
+ },
+
+ toString: function(){
+ var params = "x y z rotationX rotationY".split(" ")
+ return this.__toString(params, "scene.camera.move")
+ },
+
+ getCameraEuler: function (target) {
+ var dx = target.x - this.x,
+ dy = target.y - this.y,
+ dz = target.z - this.z
+ r = {}
+ r.y = Math.atan2(-dx, dz)
+ r.x = Math.atan2(-dy, Math.sqrt(dx*dx + dz*dz))
+ r.z = 0
+ if (MX.rotationUnit === 'deg') {
+ r.x = MX.toDeg(r.x)
+ r.y = MX.toDeg(r.y)
+ }
+ return r
+ }
+
+})
+
+MX.Scene = (function () {
+
+ var add = MX.Object3D.prototype.add,
+ remove = MX.Object3D.prototype.remove
+
+ function Scene () {
+
+ this.el = document.createElement('div')
+ this.el.classList.add('mx-scene')
+
+ var s = this.el.style
+
+ s[MX.transformProp] = 'preserve-3d'
+
+ s.webkitPerspectiveOrigin = '50% 50%'
+ s.mozPerspectiveOrigin = '50% 50%'
+ s.perspectiveOrigin = '50% 50%'
+
+ s.webkitUserSelect = 'none'
+ s.mozUserSelect = 'none'
+ s.userSelect = 'none'
+
+ s.overflow = 'hidden'
+
+ this.inner = new MX.Object3D().addTo(this.el)
+ this.inner.el.style.width = '0'
+ this.inner.el.style.height = '0'
+
+ var self = this
+ var width, height, perspective
+
+ Object.defineProperty(this, 'width', {
+ get: function () {
+ return width
+ },
+ set: function (val) {
+ width = val
+ self.el.style.width = val + 'px'
+ }
+ })
+
+ Object.defineProperty(this, 'height', {
+ get: function () {
+ return height
+ },
+ set: function (val) {
+ height = val
+ self.el.style.height = val + 'px'
+ }
+ })
+
+ Object.defineProperty(this, 'perspective', {
+ get: function () {
+ return perspective
+ },
+ set: function (val) {
+ perspective = val
+ self.el.style[MX.perspectiveProp] = val + 'px'
+ self.inner.z = -val - self.camera.z
+ self.inner.rotationOrigin.z = -val
+ }
+ })
+
+ var cam = this.camera = new MX.Camera()
+
+ this.inner.rotationOrigin = { x:0, y:0, z:0 }
+
+ this.perspective = 0
+ }
+
+ Scene.prototype = {
+
+ constructor: Scene,
+
+ add: function () {
+ add.apply(this.inner, arguments)
+ return this
+ },
+
+ remove: function () {
+ remove.apply(this.inner, arguments)
+ return this
+ },
+
+ addTo: function (target) {
+ if (typeof target === 'string') {
+ target = document.querySelector(target)
+ }
+ if (target instanceof HTMLElement && target.appendChild) {
+ target.appendChild(this.el)
+ } else {
+ console.warn('You can only add a Scene to an HTML element.')
+ }
+ return this
+ },
+
+ update: function () {
+ // update inner based on camera
+
+ var i = this.inner,
+ c = this.camera
+
+ c.update()
+
+ i.z = -this.perspective - c.z
+ i.x = -c.x
+ i.y = -c.y
+
+ i.rotationX = -c.rotationX
+ i.rotationY = -c.rotationY
+ //i.rotationZ = -c.rotationZ
+
+ i.update()
+ return this
+ },
+
+ }
+
+ return Scene
+
+})() \ No newline at end of file
diff --git a/assets/javascripts/mx/extensions/mx.scrubber.js b/assets/javascripts/mx/extensions/mx.scrubber.js
new file mode 100644
index 0000000..54612f2
--- /dev/null
+++ b/assets/javascripts/mx/extensions/mx.scrubber.js
@@ -0,0 +1,191 @@
+/*
+ Use the scrollwheel to tween between different points and orientations
+
+ scrubber = new MX.Scrubber(cam, [
+ {
+ position: [0, viewHeight, -1000],
+ rotation: [0, 0]
+ },
+ {
+ position: [0, 1000, 1000],
+ rotation: [0, Math.PI]
+ },
+ {
+ position: [0, viewHeight, -1000],
+ rotation: [0, 2*Math.PI]
+ },
+ {
+ position: [0, viewHeight, -2000],
+ rotation: [0, 0]
+ }
+ ])
+
+ // in your animate function:
+ scrubber.update()
+
+*/
+
+MX.Scrubber = function (obj, points) {
+
+ obj = obj || {}
+ points = points || {}
+
+ var reversible = true, loop = false;
+
+ var total = points.length * 100,
+ distance = 0
+ destination = 0,
+ last_index = -1,
+ last_name = null,
+ locked = false,
+ point_count = points.length + (loop+0)
+
+ var avg_speed = scroll_avg_speed = 5,
+ click_avg_speed = 20,
+ webkit_ratio = 0.02
+
+ if (points[0].position) {
+ points.forEach(function(p){
+ p.x = p.position[0]
+ p.y = p.position[1]
+ p.z = p.position[2]
+ p.rotationX = p.rotation[0]
+ p.rotationY = p.rotation[1]
+ })
+ }
+
+ document.addEventListener('touchstart', next, false);
+ document.addEventListener('mousedown', next, false);
+ document.addEventListener('mousewheel', onDocumentMouseWheel, false);
+ document.addEventListener('DOMMouseScroll', onDocumentMouseWheel, false);
+ function onDocumentMouseWheel (e) {
+
+ if (locked) return
+
+ var delta = 0;
+
+ // WebKit
+ if ( event.wheelDeltaY ) {
+ delta -= event.wheelDeltaY * webkit_ratio
+ }
+ // Opera / Explorer 9
+ else if ( event.wheelDelta ) {
+ delta -= event.wheelDelta * webkit_ratio
+ }
+ // Firefox
+ else if ( event.detail ) {
+ delta += event.detail * 2
+ }
+ if (! reversible && delta < 0) return;
+
+ if (destination < total-100 || delta < 0) {
+ e.preventDefault()
+ }
+ else {
+ return
+ }
+
+ destination += delta
+
+ avg_speed = scroll_avg_speed
+ }
+
+ function add_point(point){
+ if (point.type == "Camera") {
+ point = {
+ position: [ point.x, point.y, point.z ],
+ rotation: [ point.rotationX, point.rotationY ],
+ callback: noop
+ }
+ }
+ points.push(point)
+ total = points.length * 100
+ }
+
+ function reset(){
+ distance = destination = 0
+ last_index = -1
+ last_name = null
+ }
+
+ function next(){
+ destination = ~~(destination/100) * 100
+ destination += 100
+ avg_speed = click_avg_speed
+ }
+
+ function update(){
+ if (locked) return
+
+ if (destination > total-100) destination = total-100
+
+ distance = avg(distance, destination, avg_speed)
+ var ratio = distance / total
+
+ if (! loop) {
+ if (ratio < 0) {
+ destination = 0
+ ratio = 0
+ }
+ else if (ratio > 1) {
+ destination = total
+ ratio = 1
+ }
+ }
+
+ var diff = ratio * point_count
+ var step = (distance % 100) / 100
+ var src = ~~clamp(diff, 0, point_count-1)
+ var halfway = ~~clamp(diff + 0.5, 0, point_count-1)
+ var dest = ~~clamp(diff + 1, 0, point_count-1)
+
+ if (halfway != last_index) {
+ last_index = halfway
+ if (points[last_index].name != last_name) {
+ last_name = points[last_index].name
+ }
+ $("#info .active").removeClass("active")
+ $("#info div[data-name='" + last_name + "']").addClass("active")
+ points[halfway].callback && points[halfway].callback()
+ }
+
+ var ry0 = points[src].rotationY
+ var ry1 = points[dest].rotationY
+ if (abs(ry0 - ry1) == TWO_PI) {
+ ry0 = ry1
+ }
+
+ obj.x = lerp(step, points[src].x, points[dest].x)
+ obj.y = lerp(step, points[src].y, points[dest].y)
+ obj.z = lerp(step, points[src].z, points[dest].z)
+ obj.rotationX = lerp(step, points[src].rotationX, points[dest].rotationX)
+ obj.rotationY = lerp(step, ry0, ry1)
+ if (obj.rotationY > PI) { obj.rotationY -= TWO_PI }
+ }
+
+ var scrubber = {
+ init: function(){
+ app && app.movements && app.movements.lock()
+ },
+ lock: function(){
+ app && app.movements && app.movements.unlock()
+ locked = true
+ },
+ unlock: function(){
+ app && app.movements && app.movements.lock()
+ locked = false
+ },
+ step: function(n){
+ distance = destination = n * 100
+ },
+ add_point: add_point,
+ reset: reset,
+ next: next,
+ update: update,
+
+ obj: obj,
+ points: points
+ }
+
+ return scrubber;
+}
diff --git a/assets/javascripts/mx/mx.js b/assets/javascripts/mx/mx.js
new file mode 100644
index 0000000..b7d0bca
--- /dev/null
+++ b/assets/javascripts/mx/mx.js
@@ -0,0 +1,582 @@
+/**
+ * Copyright (C) 2013 by Evan You
+ *
+ * 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.
+ */
+
+var MX = MX || (function (undefined) {
+
+ var MX = {
+ version: '0.1.0',
+ prefix: undefined,
+ rotationUnit: 'rad'
+ }
+
+ var floatPrecision = 5
+
+ // ========================================================================
+ // Setup & Compatibility
+ // ========================================================================
+
+ var transformProp,
+ transitionProp,
+ transformOriginProp,
+ transformStyleProp,
+ perspectiveProp,
+ transitionEndEvent
+
+ var positionAtCenter = true, // whether to auto center objects
+ centeringCSS // styles to inject for center positioning
+
+ document.addEventListener('DOMContentLoaded', setup)
+
+ function setup () {
+
+ // sniff prefix
+
+ var s = document.body.style
+
+ MX.prefix =
+ 'webkitTransform' in s ? 'webkit' :
+ 'mozTransform' in s ? 'moz' :
+ 'msTransform' in s ? 'ms' : ''
+
+ transformProp = MX.transformProp = addPrefix('transform')
+ transitionProp = MX.transitionProp = addPrefix('transition')
+ transformOriginProp = MX.transformOriginProp = addPrefix('transformOrigin')
+ transformStyleProp = MX.transformStyleProp = addPrefix('transformStyle')
+ perspectiveProp = MX.perspectiveProp = addPrefix('perspective')
+ transitionEndEvent = MX.transitionEndEvent = MX.prefix === 'webkit' ? 'webkitTransitionEnd' : 'transitionend'
+
+ // shiv rAF
+
+ var vendors = ['webkit', 'moz', 'ms']
+ 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']
+ }
+
+ // inject centering css
+
+ centeringCSS = document.createElement('style')
+ centeringCSS.type = 'text/css'
+ centeringCSS.innerHTML =
+ '.mx-object3d {'
+ + 'position: absolute;'
+ + 'top: 50%;'
+ + 'left: 50%;}'
+ injectCenteringCSS()
+
+ window.scrollTo(0,0)
+ }
+
+ function injectCenteringCSS () {
+ document.head.appendChild(centeringCSS)
+ }
+
+ function removeCenteringCSS () {
+ document.head.removeChild(centeringCSS)
+ }
+
+ // ========================================================================
+ // Utils
+ // ========================================================================
+
+ function toDeg (rad) {
+ return rad / Math.PI * 180
+ }
+
+ function toRad (deg) {
+ return deg / 180 * Math.PI
+ }
+
+ function buildRotationTranslation (obj) {
+
+ // used when rotationOrigin is set
+
+ var origin = obj.rotationOrigin
+ if (!origin) {
+ return
+ } else {
+ var dx = origin.x - obj.x,
+ dy = -(origin.y - obj.y),
+ dz = -(origin.z - obj.z)
+ return {
+ before: 'translate3d(' + dx.toFixed(floatPrecision) +'px,' + dy.toFixed(floatPrecision) + 'px,' + dz.toFixed(floatPrecision) + 'px) ',
+ after: 'translate3d(' + (-dx).toFixed(floatPrecision) + 'px,' + (-dy).toFixed(floatPrecision) + 'px,' + (-dz).toFixed(floatPrecision) + 'px) '
+ }
+ }
+ }
+
+ function addPrefix (string) {
+ if (MX.prefix) {
+ string = MX.prefix + string.charAt(0).toUpperCase() + string.slice(1)
+ }
+ return string
+ }
+
+ // ========================================================================
+ // Base Object3D
+ // ========================================================================
+
+ function Object3D (el) {
+
+ this.setupDomElement(el)
+ this.setCSSTransformStyle('preserve-3d')
+ this.el.classList.add('mx-object3d')
+
+ this.parent = undefined
+ this.children = []
+ this.updateChildren = true
+
+ this.inverseLookAt = false
+
+ this.persisted = true
+
+ this.reset()
+
+// this.quaternion = new MX.Quaternion ()
+// this.quaternion._euler = this
+
+ var width, height,
+ self = this
+
+ Object.defineProperty(this, 'width', {
+ get: function () {
+ return width
+ || parseInt(self.el.style.width, 10)
+ || 0
+ },
+ set: function (val) {
+ width = val
+ this.el.style.width = width + 'px'
+ }
+ })
+
+ Object.defineProperty(this, 'height', {
+ get: function () {
+ return height
+ || parseInt(self.el.style.height, 10)
+ || 0
+ },
+ set: function (val) {
+ height = val
+ this.el.style.height = height + 'px'
+ }
+ })
+ }
+
+ Object3D.prototype = {
+
+ constructor: Object3D,
+
+ reset: function () {
+ this.x = this.__x = 0
+ this.y = this.__y = 0
+ this.z = this.__z = 0
+ this.rotationX = this.__rotationX = 0
+ this.rotationY = this.__rotationY = 0
+ this.rotationZ = this.__rotationZ = 0
+ this.scaleX = this.__scaleX = 1
+ this.scaleY = this.__scaleY = 1
+ this.scaleZ = this.__scaleZ = 1
+ this.scale = this.__scale = 1
+ this.perspective = this.__perspective = 0
+ this.rotationOrigin = undefined
+ this.followTarget = undefined
+// this.quaternion = new MX.Quaternion()
+ this.dirty = true
+ this.update()
+ },
+
+ setupDomElement: function (el) {
+ this.el = undefined
+ if (el instanceof HTMLElement) {
+ this.el = el
+ } else if (typeof el === 'string') {
+ var tag = el.match(/^[^.#\s]*/)[1],
+ id = el.match(/#[^.#\s]*/),
+ classes = el.match(/\.[^.#\s]*/g)
+ this.el = document.createElement(tag || 'div')
+ if (id) {
+ this.el.id = id[0].slice(1)
+ }
+ if (classes) {
+ var i = classes.length
+ while (i--) {
+ this.el.classList.add(classes[i].slice(1))
+ }
+ }
+ } else {
+ this.el = document.createElement('div')
+ }
+ },
+
+ update: function () {
+
+ if (this.updateChildren) {
+ var i = this.children.length
+ while (i--) {
+ this.children[i].update()
+ }
+ }
+
+ if (this.followTarget) {
+ this.lookAt(this.followTarget, false)
+ }
+
+ if (this.scaleX !== this.__scaleX ||
+ this.scaleY !== this.__scaleY ||
+ this.scaleZ !== this.__scaleZ) {
+ this.__scaleX = this.scaleX
+ this.__scaleY = this.scaleY
+ this.__scaleZ = this.scaleZ
+ this.dirty = true
+ }
+
+ if (this.scale !== this.__scale) {
+ this.scaleX =
+ this.scaleY =
+ this.scaleZ =
+ this.__scaleX =
+ this.__scaleY =
+ this.__scaleZ =
+ this.__scale =
+ this.scale
+ this.dirty = true
+ }
+
+ if (this.rotationX !== this.__rotationX ||
+ this.rotationY !== this.__rotationY ||
+ this.rotationZ !== this.__rotationZ) {
+ this.__rotationX = this.rotationX
+ this.__rotationY = this.rotationY
+ this.__rotationZ = this.rotationZ
+ this.dirty = true
+ }
+
+ if (this.x !== this.__x ||
+ this.y !== this.__y ||
+ this.z !== this.__z) {
+ this.__x = this.x
+ this.__y = this.y
+ this.__z = this.z
+ this.dirty = true
+ }
+
+ if (this.perspective !== this.__perspective) {
+ this.__perspective = this.perspective
+ this.dirty = true
+ }
+
+ if (this.dirty && this.el) {
+
+ var rotationTranslation = buildRotationTranslation(this),
+ rotation = 'rotateX(' + this.rotationX.toFixed(floatPrecision) + MX.rotationUnit + ') '
+ + 'rotateY(' + this.rotationY.toFixed(floatPrecision) + MX.rotationUnit + ') '
+ + 'rotateZ(' + this.rotationZ.toFixed(floatPrecision) + MX.rotationUnit + ') '
+
+ var transformString =
+ (MX.positionAtCenter ? 'translate3d(-50%, -50%, 0) ' : '')
+ + (this.perspective ? 'perspective(' + this.perspective + 'px) ' : '')
+ + 'translate3d('
+ + this.x.toFixed(floatPrecision || 0) + 'px,'
+ + (-this.y).toFixed(floatPrecision) + 'px,'
+ + (-this.z).toFixed(floatPrecision) + 'px) '
+ + 'scale3d('
+ + this.scaleX.toFixed(floatPrecision) + ','
+ + this.scaleY.toFixed(floatPrecision) + ','
+ + this.scaleZ.toFixed(floatPrecision) + ') '
+
+ if (rotationTranslation) {
+ transformString += rotationTranslation.before
+ + rotation
+ + rotationTranslation.after
+
+ } else {
+ transformString += rotation
+ }
+
+ this.el.style[transformProp] = transformString
+ this.dirty = false
+ }
+
+ return this
+
+ },
+
+ // taken from three.js
+ setFromQuaternion: function ( q, order, update ) {
+ // q is assumed to be normalized
+
+ // http://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/content/SpinCalc.m
+
+ var sqx = q.x * q.x;
+ var sqy = q.y * q.y;
+ var sqz = q.z * q.z;
+ var sqw = q.w * q.w;
+
+ this.rotationX = Math.atan2( 2 * ( q.x * q.w - q.y * q.z ), ( sqw - sqx - sqy + sqz ) );
+ this.rotationY = Math.asin( clamp( 2 * ( q.x * q.z + q.y * q.w ), -1, 1 ) );
+ this.rotationZ = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw + sqx - sqy - sqz ) );
+ },
+
+ lookAt: function (target, update) {
+ var r = this.getLookAtEuler(target)
+ this.setRotation(r)
+ if (update !== false) this.update()
+ return this
+ },
+
+ getLookAtEuler: function (target) {
+ // euler order XYZ
+ var r = {},
+ dx = target.x - this.x,
+ dy = target.y - this.y,
+ dz = target.z - this.z
+ if (this.inverseLookAt) {
+ dx = -dx
+ dy = -dy
+ dz = -dz
+ }
+ if (dz === 0) dz = 0.001
+ r.x = -Math.atan2(dy, dz)
+ var flip = dz > 0 ? 1 : -1
+ r.y = flip * Math.atan2(dx * Math.cos(r.x), dz * -flip)
+ r.z = Math.atan2(Math.cos(r.x), Math.sin(r.x) * Math.sin(r.y)) - Math.PI / 2
+ if (MX.rotationUnit === 'deg') {
+ r.x = toDeg(r.x)
+ r.y = toDeg(r.y)
+ r.z = toDeg(r.z)
+ }
+ return r
+ },
+
+ add: function () {
+ if (!this.el) return
+ var parent = this
+ Array.prototype.forEach.call(arguments, function (child) {
+ if (!child instanceof Object3D) return
+ parent.el.appendChild(child.el)
+ if (!parent.children) parent.children = []
+ parent.children.push(child)
+ child.parent = parent
+ })
+ return this
+ },
+
+ remove: function () {
+ var parent = this
+ Array.prototype.forEach.call(arguments, function (child) {
+ var index = parent.children.indexOf(child)
+ if (index !== -1) {
+ parent.children.splice(index, 1)
+ child.parent = undefined
+ }
+ })
+ return this
+ },
+
+ addTo: function (target) {
+ if (typeof target === 'string') {
+ target = document.querySelector(target)
+ }
+ if (target instanceof HTMLElement && target.appendChild) {
+ target.appendChild(this.el)
+ } else if (target instanceof Object3D || target instanceof Scene) {
+ target.add(this)
+ }
+ return this
+ },
+
+ removeElement: function () {
+ if (this.el.parentNode) {
+ this.el.parentNode.removeChild(this.el)
+ }
+ },
+
+ setPosition: function (tar) {
+ this.x = (tar.x || tar.x === 0) ? tar.x : this.x
+ this.y = (tar.y || tar.y === 0) ? tar.y : this.y
+ this.z = (tar.z || tar.z === 0) ? tar.z : this.z
+ },
+
+ setRotation: function (tar) {
+ this.rotationX = (tar.x || tar.x === 0) ? tar.x : this.rotationX
+ this.rotationY = (tar.y || tar.y === 0) ? tar.y : this.rotationY
+ this.rotationZ = (tar.z || tar.z === 0) ? tar.z : this.rotationZ
+ },
+
+ setScale: function (tar) {
+ this.scaleX = (tar.x || tar.x === 0) ? tar.x : this.scaleX
+ this.scaleY = (tar.y || tar.y === 0) ? tar.y : this.scaleY
+ this.scaleZ = (tar.z || tar.z === 0) ? tar.z : this.scaleZ
+ },
+
+ setCSSTransformOrigin: function (origin) {
+ this.el && (this.el.style[transformOriginProp] = origin)
+ return this
+ },
+
+ setCSSTransformStyle: function (style) {
+ this.el && (this.el.style[transformStyleProp] = style)
+ return this
+ },
+
+ setCSSTransition: function (trans) {
+ this.el && (this.el.style[transitionProp] = trans)
+ return this
+ },
+
+ setCSSPerspective: function (pers) {
+ this.el && (this.el.style[perspectiveProp] = pers)
+ return this
+ },
+
+ onTransitionEnd: function (callback) {
+ this.cancelTransitionEnd()
+ var el = this.el
+ el.addEventListener(transitionEndEvent, onEnd)
+ function onEnd () {
+ el.removeEventListener(transitionEndEvent, onEnd)
+ callback()
+ }
+ },
+
+ cancelTransitionEnd: function () {
+ this.el.removeEventListener(transitionEndEvent)
+ },
+
+ toString: function(params){
+ params = params || "id width height depth x y z rotationX rotationY rotationZ scale".split(" ")
+ return this.__toString(params)
+ },
+
+ __toString: function(params, func){
+ this.id = this.id || guid()
+ var list = [],
+ obj = {},
+ type = this.type || "Object3d",
+ name = type.toLowerCase(),
+ val,
+ param
+ for (var i in params) {
+ param = params[i]
+ val = this[param]
+ if (val === 0 && ! func) continue;
+ if (typeof val == "number") {
+ if (param.indexOf("rotation") != -1) {
+ obj[param] = Number(val.toFixed(3))
+ }
+ else {
+ obj[param] = ~~val
+ }
+ }
+ else {
+ obj[param] = val
+ }
+ }
+
+ return (func || "var " + name + " = new MX." + type ) + "(" +
+ JSON.stringify(obj, undefined, 2) +
+ ")\n" + (func ? "" : "scene.add(" + name + ")")
+ },
+
+ contains: function(x,y,z){
+ var containsX = false,
+ containsY = false,
+ containsZ = false
+
+ if (x === null) {
+ containsX = true
+ }
+ else {
+ containsX = abs(this.x - x) <= this.width/2
+ }
+
+ if (y === null) {
+ containsY = true
+ }
+ else {
+ containsY = abs(this.y - y) <= this.height/2
+ }
+
+ if (z === null) {
+ containsZ = true
+ }
+ else {
+ containsZ = abs(this.z - z) <= this.depth/2
+ }
+
+ return (containsX && containsY && containsZ)
+ }
+
+ }
+
+ // ========================================================================
+ // Inheritance
+ // ========================================================================
+
+ Object3D.extend = extend.bind(Object3D)
+
+ function extend (props) {
+ var Super = this
+ var ExtendedObject3D = function () {
+ Super.call(this)
+ props.init && props.init.apply(this, arguments)
+ }
+ ExtendedObject3D.prototype = Object.create(Super.prototype)
+ for (var prop in props) {
+ if (props.hasOwnProperty(prop) && prop !== 'init') {
+ ExtendedObject3D.prototype[prop] = props[prop]
+ }
+ }
+ ExtendedObject3D.extend = extend.bind(ExtendedObject3D)
+ return ExtendedObject3D
+ }
+
+ // ========================================================================
+ // Expose API
+ // ========================================================================
+
+ MX.Object3D = Object3D
+ MX.toRad = toRad
+ MX.toDeg = toDeg
+
+ // center positioning getter setter
+ Object.defineProperty(MX, 'positionAtCenter', {
+ get: function () {
+ return positionAtCenter
+ },
+ set: function (val) {
+ if (typeof val !== 'boolean') return
+ positionAtCenter = val
+ if (positionAtCenter) {
+ injectCenteringCSS()
+ } else {
+ removeCenteringCSS()
+ }
+ }
+ })
+
+ return MX
+
+})() \ No newline at end of file
diff --git a/assets/javascripts/mx/mx.min.js b/assets/javascripts/mx/mx.min.js
new file mode 100644
index 0000000..b0f0cdd
--- /dev/null
+++ b/assets/javascripts/mx/mx.min.js
@@ -0,0 +1 @@
+var MX=MX||function(undefined){var MX={prefix:undefined,rotationUnit:"rad"};var floatPrecision=5;var transformProp,transitionProp,transformOriginProp,transformStyleProp,perspectiveProp;var positionAtCenter=true,centeringCSS;document.addEventListener("DOMContentLoaded",setup);function setup(){var s=document.body.style;MX.prefix="webkitTransform"in s?"webkit":"mozTransform"in s?"moz":"msTransform"in s?"ms":"";transformProp=MX.transformProp=addPrefix("transform");transitionProp=MX.transitionProp=addPrefix("transition");transformOriginProp=MX.transformOriginProp=addPrefix("transformOrigin");transformStyleProp=MX.transformStyleProp=addPrefix("transformStyle");perspectiveProp=MX.perspectiveProp=addPrefix("perspective");var vendors=["webkit","moz","ms"];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"]}centeringCSS=document.createElement("style");centeringCSS.type="text/css";centeringCSS.innerHTML=".mx-object3d {"+"position: absolute;"+"top: 50%;"+"left: 50%;}";injectCenteringCSS()}function injectCenteringCSS(){document.head.appendChild(centeringCSS)}function removeCenteringCSS(){document.head.removeChild(centeringCSS)}function toDeg(rad){return rad/Math.PI*180}function toRad(deg){return deg/180*Math.PI}function buildRotationTranslation(obj){var origin=obj.rotationOrigin;if(!origin){return}else{var dx=origin.x-obj.x,dy=-(origin.y-obj.y),dz=-(origin.z-obj.z);return{before:"translate3d("+dx+"px,"+dy+"px,"+dz+"px) ",after:"translate3d("+-dx+"px,"+-dy+"px,"+-dz+"px) "}}}function addPrefix(string){if(MX.prefix){string=MX.prefix+string.charAt(0).toUpperCase()+string.slice(1)}return string}function Object3D(el){this.setupDomElement(el);this.setCSSTransformStyle("preserve-3d");this.el.classList.add("mx-object3d");this.parent=undefined;this.children=[];this.updateChildren=true;this.inverseLookAt=false;this.reset();var width,height,self=this;Object.defineProperty(this,"width",{get:function(){return width||parseInt(self.el.style.width,10)||0},set:function(val){width=val;this.el.style.width=width+"px"}});Object.defineProperty(this,"height",{get:function(){return height||parseInt(self.el.style.height,10)||0},set:function(val){height=val;this.el.style.height=height+"px"}})}Object3D.prototype={constructor:Object3D,reset:function(){this.x=this.__x=0;this.y=this.__y=0;this.z=this.__z=0;this.rotationX=this.__rotationX=0;this.rotationY=this.__rotationY=0;this.rotationZ=this.__rotationZ=0;this.scaleX=this.__scaleX=1;this.scaleY=this.__scaleY=1;this.scaleZ=this.__scaleZ=1;this.scale=this.__scale=1;this.rotationOrigin=undefined;this.followTarget=undefined;this.dirty=true;this.update()},setupDomElement:function(el){this.el=undefined;if(el instanceof HTMLElement){this.el=el}else if(typeof el==="string"){var tag=el.match(/^[^.#\s]*/)[1],id=el.match(/#[^.#\s]*/),classes=el.match(/\.[^.#\s]*/g);this.el=document.createElement(tag||"div");if(id){this.el.id=id[0].slice(1)}if(classes){var i=classes.length;while(i--){this.el.classList.add(classes[i].slice(1))}}}else{this.el=document.createElement("div")}},update:function(){if(this.updateChildren){var i=this.children.length;while(i--){this.children[i].update()}}if(this.followTarget){this.lookAt(this.followTarget,false)}if(this.scaleX!==this.__scaleX||this.scaleY!==this.__scaleY||this.scaleZ!==this.__scaleZ){this.__scaleX=this.scaleX;this.__scaleY=this.scaleY;this.__scaleZ=this.scaleZ;this.dirty=true}if(this.scale!==this.__scale){this.scaleX=this.scaleY=this.scaleZ=this.__scaleX=this.__scaleY=this.__scaleZ=this.__scale=this.scale;this.dirty=true}if(this.rotationX!==this.__rotationX||this.rotationY!==this.__rotationY||this.rotationZ!==this.__rotationZ){this.__rotationX=this.rotationX;this.__rotationY=this.rotationY;this.__rotationZ=this.rotationZ;this.dirty=true}if(this.x!==this.__x||this.y!==this.__y||this.z!==this.__z){this.__x=this.x;this.__y=this.y;this.__z=this.z;this.dirty=true}if(this.dirty&&this.el){var rotationTranslation=buildRotationTranslation(this),rotation="rotateX("+this.rotationX.toFixed(floatPrecision)+MX.rotationUnit+") "+"rotateY("+this.rotationY.toFixed(floatPrecision)+MX.rotationUnit+") "+"rotateZ("+this.rotationZ.toFixed(floatPrecision)+MX.rotationUnit+") ";var transformString=(MX.positionAtCenter?"translate3d(-50%, -50%, 0) ":"")+"translate3d("+this.x.toFixed(floatPrecision)+"px,"+(-this.y).toFixed(floatPrecision)+"px,"+(-this.z).toFixed(floatPrecision)+"px) "+"scale3d("+this.scaleX.toFixed(floatPrecision)+","+this.scaleY.toFixed(floatPrecision)+","+this.scaleZ.toFixed(floatPrecision)+") ";if(rotationTranslation){transformString+=rotationTranslation.before+rotation+rotationTranslation.after}else{transformString+=rotation}this.el.style[transformProp]=transformString;this.dirty=false}return this},lookAt:function(target,update){var r=this.getLookAtEuler(target);this.setRotation(r);if(update!==false)this.update();return this},getLookAtEuler:function(target){var r={},dx=target.x-this.x,dy=target.y-this.y,dz=target.z-this.z;if(this.inverseLookAt){dx=-dx;dy=-dy;dz=-dz}if(dz===0)dz=.001;r.x=-Math.atan2(dy,dz);var flip=dz>0?1:-1;r.y=flip*Math.atan2(dx*Math.cos(r.x),dz*-flip);r.z=Math.atan2(Math.cos(r.x),Math.sin(r.x)*Math.sin(r.y))-Math.PI/2;if(MX.rotationUnit==="deg"){r.x=toDeg(r.x);r.y=toDeg(r.y);r.z=toDeg(r.z)}return r},add:function(){if(!this.el)return;var parent=this;Array.prototype.forEach.call(arguments,function(child){if(!child instanceof Object3D)return;parent.el.appendChild(child.el);if(!parent.children)parent.children=[];parent.children.push(child);child.parent=parent});return this},remove:function(){var parent=this;Array.prototype.forEach.call(arguments,function(child){var index=parent.children.indexOf(child);if(index!==-1){parent.children.splice(index,1);child.parent=undefined}});return this},addTo:function(target){if(typeof target==="string"){target=document.querySelector(target)}if(target instanceof HTMLElement&&target.appendChild){target.appendChild(this.el)}else if(target instanceof Object3D||target instanceof Scene){target.add(this)}return this},removeElement:function(){if(this.el.parentNode){this.el.parentNode.removeChild(this.el)}},setPosition:function(tar){this.x=tar.x||tar.x===0?tar.x:this.x;this.y=tar.y||tar.y===0?tar.y:this.y;this.z=tar.z||tar.z===0?tar.z:this.z},setRotation:function(tar){this.rotationX=tar.x||tar.x===0?tar.x:this.rotationX;this.rotationY=tar.y||tar.y===0?tar.y:this.rotationY;this.rotationZ=tar.z||tar.z===0?tar.z:this.rotationZ},setScale:function(tar){this.scaleX=tar.x||tar.x===0?tar.x:this.scaleX;this.scaleY=tar.y||tar.y===0?tar.y:this.scaleY;this.scaleZ=tar.z||tar.z===0?tar.z:this.scaleZ},setCSSTransformOrigin:function(origin){this.el&&(this.el.style[transformOriginProp]=addPrefix(origin));return this},setCSSTransformStyle:function(style){this.el&&(this.el.style[transformStyleProp]=addPrefix(style));return this},setCSSTransition:function(trans){this.el&&(this.el.style[transitionProp]=addPrefix(trans));return this},setCSSPerspective:function(pers){this.el&&(this.el.style[perspectiveProp]=addPrefix(pers));return this}};Object3D.extend=extend.bind(Object3D);function extend(props){var Super=this;var ExtendedObject3D=function(){Super.call(this);props.init&&props.init.apply(this,arguments)};ExtendedObject3D.prototype=Object.create(Super.prototype);for(var prop in props){if(props.hasOwnProperty(prop)&&prop!=="init"){ExtendedObject3D.prototype[prop]=props[prop]}}ExtendedObject3D.extend=extend.bind(ExtendedObject3D);return ExtendedObject3D}MX.Object3D=Object3D;MX.toRad=toRad;MX.toDeg=toDeg;Object.defineProperty(MX,"positionAtCenter",{get:function(){return positionAtCenter},set:function(val){if(typeof val!=="boolean")return;positionAtCenter=val;if(positionAtCenter){injectCenteringCSS()}else{removeCenteringCSS()}}});return MX}(); \ No newline at end of file
diff --git a/assets/javascripts/mx/mx.minimap.js b/assets/javascripts/mx/mx.minimap.js
new file mode 100644
index 0000000..252305c
--- /dev/null
+++ b/assets/javascripts/mx/mx.minimap.js
@@ -0,0 +1,211 @@
+MX.Minimap = function () {
+ var canvas = document.createElement("canvas")
+ var ctx = canvas.getContext("2d")
+ var w = canvas.width = 200
+ var h = canvas.height = 200
+
+ var gridSpace;
+ var zoom = 2.7
+
+ var gridStroke = '#ddd'
+ var boxFill = '#fff'
+ var boxStroke = '#000'
+ var playerColor = '#888'
+
+ var xmin, xmax, ymin, ymax, xpos, ypos, scale, side;
+
+ this.update = function(){
+ this.draw()
+ }
+
+ this.bounds = function(){
+ gridSpace = Math.pow(10, ~~(zoom-0.5)+0.5)
+ side = Math.pow(10, zoom+1)
+ scale = w / side
+ xpos = -cam.x
+ ypos = cam.z
+
+ xmin = side/-2 - xpos
+ xmax = side/2 - xpos
+ ymin = side/-2 - ypos
+ ymax = side/2 - ypos
+ }
+
+ this.draw = function(){
+ ctx.clearRect(0,0,w,h)
+
+ ctx.fillStyle = "#fff"
+ ctx.fillRect(0,0,w,h)
+ this.bounds()
+ this.grid()
+ this.boxes()
+ this.player()
+ }
+
+ this.grid = function(){
+ ctx.strokeStyle = gridStroke
+ ctx.lineWidth = 1
+ ctx.fillStyle = "transparent"
+
+ var xmod = xmin-(xmin % gridSpace)
+ var ymod = ymin-(ymin % gridSpace)
+
+ for (var x = xmin; x < xmax+gridSpace; x += gridSpace) {
+ var xline = (x-xmod) * scale
+ line(xline, 0, xline, h)
+ }
+ for (var y = ymin; y < ymax+gridSpace; y += gridSpace) {
+ var yline = (y-ymod) * scale
+ line(0, yline, w, yline)
+ }
+
+ function line(x0,y0,x1,y1) {
+ ctx.beginPath()
+ ctx.moveTo(x0, y0)
+ ctx.lineTo(x1, y1)
+ ctx.stroke()
+ }
+ }
+ this.player = function(){
+ ctx.save()
+
+ ctx.translate(~~(w/2),~~(h/2));
+ ctx.rotate(-cam.rotationY)
+
+ var radius = 5
+
+ ctx.fillStyle = playerColor;
+
+ ctx.beginPath();
+ ctx.arc(0, 0, radius, 0, 2*Math.PI, false);
+ ctx.fill();
+
+ ctx.beginPath();
+ ctx.moveTo(0,0)
+ ctx.lineTo(-radius,0)
+ ctx.lineTo(0,radius*3)
+ ctx.lineTo(radius,0)
+ ctx.moveTo(0,0)
+ ctx.fill()
+
+ ctx.fillStyle = "transparent"
+ ctx.restore()
+ }
+
+ this.boxes = function(){
+
+ ctx.save()
+ ctx.translate(~~(w/2),~~(h/2));
+ ctx.lineWidth = 0.5
+ var tx = ((-xpos) * scale),
+ ty = ((-ypos) * scale);
+ ctx.translate(tx, ty)
+
+ scene.inner.children.forEach(function(obj){
+ if (obj.type == "BoxDimensions") {
+
+ ctx.save()
+ ctx.fillStyle = obj.color
+ ctx.strokeStyle = obj.borderColor
+
+ var obj_scale = (obj.scale || 1) * scale
+
+ var tx = ~~((obj.x) * obj_scale),
+ ty = ~~((obj.z) * obj_scale);
+ ctx.translate(-tx, ty)
+ ctx.rotate(-obj.rotationY)
+
+ var ww = ~~(obj.width/2 * obj_scale)
+ var hh = ~~(obj.depth/2 * obj_scale)
+ ctx.beginPath();
+ ctx.moveTo(ww, hh)
+
+ ctx.lineTo(ww, -hh)
+ ctx.lineTo(-ww, -hh)
+ ctx.lineTo(-ww, hh)
+ ctx.closePath()
+ ctx.fill()
+ ctx.stroke()
+ ctx.restore()
+ }
+ if (obj.type == "Image") {
+
+ ctx.save()
+ ctx.strokeStyle = "#444"
+
+ var obj_scale = (obj.scale || 1) * scale
+
+ var tx = ~~((obj.x) * obj_scale),
+ ty = ~~((obj.z) * obj_scale);
+ ctx.translate(-tx, ty)
+ ctx.rotate(-obj.rotationY)
+
+ var ww = ~~(obj.width/2 * obj_scale)
+ ctx.beginPath();
+ ctx.moveTo(ww, 0)
+ ctx.lineTo(-ww, 0)
+ ctx.closePath()
+ ctx.stroke()
+ ctx.restore()
+ }
+ })
+ ctx.restore()
+ }
+
+ var dragging = false, mx = 0, my = 0, mdx = 0, mdy = 0, cx, cy;
+ canvas.addEventListener("mousedown", function(e){
+ e.stopPropagation()
+ var rect = canvas.getBoundingClientRect()
+ dragging = true;
+ mx = e.pageX - rect.left
+ my = e.pageY - rect.top
+ mdx = (mx - w/2) / scale
+ mdy = (my - h/2) / scale
+ cx = cam.x // -= mdx
+ cy = cam.z // += mdy
+
+ minimap.update()
+ })
+ document.addEventListener("mousemove", function(e){
+ if (dragging) {
+ e.stopPropagation()
+ var rect = canvas.getBoundingClientRect()
+ var mnx = e.pageX - rect.left
+ var mny = e.pageY - rect.top
+ mdx = (mnx - mx) / scale
+ mdy = (mny - my) / scale
+
+ cam.x = cx + mdx
+ cam.z = cy - mdy
+ minimap.update()
+ }
+ })
+ document.addEventListener("mouseup", function(e){
+ dragging = false;
+ })
+
+ canvas.addEventListener( 'mousewheel', onDocumentMouseWheel, false );
+ canvas.addEventListener( 'DOMMouseScroll', onDocumentMouseWheel, false);
+ function onDocumentMouseWheel (e) {
+ e.preventDefault()
+ e.stopPropagation()
+ // WebKit
+ if ( event.wheelDeltaY ) {
+ zoom -= event.wheelDeltaY * 0.0003;
+ }
+ // Opera / Explorer 9
+ else if ( event.wheelDelta ) {
+ zoom -= event.wheelDelta * 0.0003;
+ }
+ // Firefox
+ else if ( event.detail ) {
+ zoom += event.detail * 0.01;
+ }
+ minimap.update()
+ }
+
+ this.draw()
+ document.querySelector("#minimap .el").appendChild(canvas)
+
+ return this;
+} \ No newline at end of file
diff --git a/assets/javascripts/mx/mx.quaternion.js b/assets/javascripts/mx/mx.quaternion.js
new file mode 100644
index 0000000..783f887
--- /dev/null
+++ b/assets/javascripts/mx/mx.quaternion.js
@@ -0,0 +1,414 @@
+/**
+ * quaternion taken from three.js
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://exocortex.com
+ */
+
+MX.Quaternion = function ( x, y, z, w ) {
+
+ this._x = x || 0;
+ this._y = y || 0;
+ this._z = z || 0;
+ this._w = ( w !== undefined ) ? w : 1;
+
+};
+
+MX.Quaternion.prototype = {
+
+ constructor: MX.Quaternion,
+
+ _x: 0,_y: 0, _z: 0, _w: 0,
+
+ _euler: undefined,
+
+ _updateEuler: function ( callback ) {
+
+ if ( this._euler !== undefined ) {
+
+ this._euler.setFromQuaternion( this, undefined, false );
+
+ }
+
+ },
+
+ get x () {
+
+ return this._x;
+
+ },
+
+ set x ( value ) {
+
+ this._x = value;
+ this._updateEuler();
+
+ },
+
+ get y () {
+
+ return this._y;
+
+ },
+
+ set y ( value ) {
+
+ this._y = value;
+ this._updateEuler();
+
+ },
+
+ get z () {
+
+ return this._z;
+
+ },
+
+ set z ( value ) {
+
+ this._z = value;
+ this._updateEuler();
+
+ },
+
+ get w () {
+
+ return this._w;
+
+ },
+
+ set w ( value ) {
+
+ this._w = value;
+ this._updateEuler();
+
+ },
+
+ set: function ( x, y, z, w ) {
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._w = w;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ copy: function ( quaternion ) {
+
+ this._x = quaternion._x;
+ this._y = quaternion._y;
+ this._z = quaternion._z;
+ this._w = quaternion._w;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ setFromEuler: function ( euler, update ) {
+
+ if ( euler instanceof MX.Euler === false ) {
+ throw new Error( 'ERROR: Quaternion\'s .setFromEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' );
+ }
+
+ // http://www.mathworks.com/matlabcentral/fileexchange/
+ // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
+ // content/SpinCalc.m
+
+ var c1 = Math.cos( euler._x / 2 );
+ var c2 = Math.cos( euler._y / 2 );
+ var c3 = Math.cos( euler._z / 2 );
+ var s1 = Math.sin( euler._x / 2 );
+ var s2 = Math.sin( euler._y / 2 );
+ var s3 = Math.sin( euler._z / 2 );
+
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+
+ if ( update !== false ) this._updateEuler();
+
+ return this;
+
+ },
+
+ setFromAxisAngle: function ( axis, angle ) {
+
+ // from http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
+ // axis have to be normalized
+
+ var halfAngle = angle / 2, s = Math.sin( halfAngle );
+
+ this._x = axis.x * s;
+ this._y = axis.y * s;
+ this._z = axis.z * s;
+ this._w = Math.cos( halfAngle );
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ setFromRotationMatrix: function ( m ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ var te = m.elements,
+
+ m11 = te[0], m12 = te[4], m13 = te[8],
+ m21 = te[1], m22 = te[5], m23 = te[9],
+ m31 = te[2], m32 = te[6], m33 = te[10],
+
+ trace = m11 + m22 + m33,
+ s;
+
+ if ( trace > 0 ) {
+
+ s = 0.5 / Math.sqrt( trace + 1.0 );
+
+ this._w = 0.25 / s;
+ this._x = ( m32 - m23 ) * s;
+ this._y = ( m13 - m31 ) * s;
+ this._z = ( m21 - m12 ) * s;
+
+ } else if ( m11 > m22 && m11 > m33 ) {
+
+ s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
+
+ this._w = (m32 - m23 ) / s;
+ this._x = 0.25 * s;
+ this._y = (m12 + m21 ) / s;
+ this._z = (m13 + m31 ) / s;
+
+ } else if ( m22 > m33 ) {
+
+ s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
+
+ this._w = (m13 - m31 ) / s;
+ this._x = (m12 + m21 ) / s;
+ this._y = 0.25 * s;
+ this._z = (m23 + m32 ) / s;
+
+ } else {
+
+ s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
+
+ this._w = ( m21 - m12 ) / s;
+ this._x = ( m13 + m31 ) / s;
+ this._y = ( m23 + m32 ) / s;
+ this._z = 0.25 * s;
+
+ }
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ inverse: function () {
+
+ this.conjugate().normalize();
+
+ return this;
+
+ },
+
+ conjugate: function () {
+
+ this._x *= -1;
+ this._y *= -1;
+ this._z *= -1;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ lengthSq: function () {
+
+ return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );
+
+ },
+
+ normalize: function () {
+
+ var l = this.length();
+
+ if ( l === 0 ) {
+
+ this._x = 0;
+ this._y = 0;
+ this._z = 0;
+ this._w = 1;
+
+ } else {
+
+ l = 1 / l;
+
+ this._x = this._x * l;
+ this._y = this._y * l;
+ this._z = this._z * l;
+ this._w = this._w * l;
+
+ }
+
+ return this;
+
+ },
+
+ multiply: function ( q, p ) {
+
+ if ( p !== undefined ) {
+
+ console.warn( 'DEPRECATED: Quaternion\'s .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
+ return this.multiplyQuaternions( q, p );
+
+ }
+
+ return this.multiplyQuaternions( this, q );
+
+ },
+
+ multiplyQuaternions: function ( a, b ) {
+
+ // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
+
+ var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
+ var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;
+
+ this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
+ this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
+ this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
+ this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ multiplyVector3: function ( vector ) {
+
+ console.warn( 'DEPRECATED: Quaternion\'s .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
+ return vector.applyQuaternion( this );
+
+ },
+
+ slerp: function ( qb, t ) {
+
+ var x = this._x, y = this._y, z = this._z, w = this._w;
+
+ // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
+
+ var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;
+
+ if ( cosHalfTheta < 0 ) {
+
+ this._w = -qb._w;
+ this._x = -qb._x;
+ this._y = -qb._y;
+ this._z = -qb._z;
+
+ cosHalfTheta = -cosHalfTheta;
+
+ } else {
+
+ this.copy( qb );
+
+ }
+
+ if ( cosHalfTheta >= 1.0 ) {
+
+ this._w = w;
+ this._x = x;
+ this._y = y;
+ this._z = z;
+
+ return this;
+
+ }
+
+ var halfTheta = Math.acos( cosHalfTheta );
+ var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta );
+
+ if ( Math.abs( sinHalfTheta ) < 0.001 ) {
+
+ this._w = 0.5 * ( w + this._w );
+ this._x = 0.5 * ( x + this._x );
+ this._y = 0.5 * ( y + this._y );
+ this._z = 0.5 * ( z + this._z );
+
+ return this;
+
+ }
+
+ var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
+ ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
+
+ this._w = ( w * ratioA + this._w * ratioB );
+ this._x = ( x * ratioA + this._x * ratioB );
+ this._y = ( y * ratioA + this._y * ratioB );
+ this._z = ( z * ratioA + this._z * ratioB );
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ equals: function ( quaternion ) {
+
+ return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );
+
+ },
+
+ fromArray: function ( array ) {
+
+ this._x = array[ 0 ];
+ this._y = array[ 1 ];
+ this._z = array[ 2 ];
+ this._w = array[ 3 ];
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this._x, this._y, this._z, this._w ];
+
+ },
+
+ clone: function () {
+
+ return new MX.Quaternion( this._x, this._y, this._z, this._w );
+
+ }
+
+};
+
+MX.Quaternion.slerp = function ( qa, qb, qm, t ) {
+
+ return qm.copy( qa ).slerp( qb, t );
+
+}
diff --git a/assets/javascripts/mx/primitives/mx.box.js b/assets/javascripts/mx/primitives/mx.box.js
new file mode 100644
index 0000000..dfe3f5e
--- /dev/null
+++ b/assets/javascripts/mx/primitives/mx.box.js
@@ -0,0 +1,62 @@
+MX.Box = MX.Object3D.extend({
+
+ // this will be called within the contructor
+ init: function (size, color, borderColor) {
+
+ this.type = "Box"
+
+ size = size || 100
+ color = color || 'rgba(0, 255, 122, .1)'
+ borderColor = borderColor || '#0f3'
+
+ // an Object3D's associated DOM node is the "el" property
+ this.el.classList.add('box')
+
+ var angle = MX.rotationUnit === 'deg' ? 90 : (Math.PI / 2)
+
+ var top = this.top = new MX.Object3D('.face')
+ top.rotationX = angle
+ top.y = size / 2
+
+ var bottom = this.bottom = new MX.Object3D('.face')
+ bottom.rotationX = -angle
+ bottom.y = -size / 2
+
+ var left = this.left = new MX.Object3D('.face')
+ left.rotationY = -angle
+ left.x = -size / 2
+
+ var right = this.right = new MX.Object3D('.face')
+ right.rotationY = angle
+ right.x = size / 2
+
+ var front = this.front = new MX.Object3D('.face')
+ front.z = -size / 2
+
+ var back = this.back = new MX.Object3D('.face')
+ back.rotationY = angle * 2
+ back.z = size / 2
+
+ // adding children, must also be instances of Object3D
+ this.add(top, bottom, left, right, front, back)
+
+ this.children.forEach(function (face) {
+ face.width = size - 2
+ face.height = size - 2
+ face.el.style.backgroundColor = color
+ face.el.style.border = '1px solid ' + borderColor
+ })
+
+ // this applies the updated CSS style
+ // required for any change to take effect
+ // when a parent object's update() is called
+ // all its children will be updated as well
+ this.update()
+
+ // if this object's children won't move by themselves
+ this.updateChildren = false
+ }
+
+ // other properties will be mixed into the prototype of the new constructor
+
+}) \ No newline at end of file
diff --git a/assets/javascripts/mx/primitives/mx.boxDimensions.js b/assets/javascripts/mx/primitives/mx.boxDimensions.js
new file mode 100644
index 0000000..f3edb13
--- /dev/null
+++ b/assets/javascripts/mx/primitives/mx.boxDimensions.js
@@ -0,0 +1,154 @@
+MX.BoxDimensions = MX.Object3D.extend({
+
+ // this will be called within the contructor
+ init: function (opt) {
+
+ var base = this
+
+ this.type = "BoxDimensions"
+
+ var id = this.id = opt.id || guid()
+ this.x = opt.x || 0
+ this.y = opt.y || 0
+ this.z = opt.z || 0
+ // this.scale = opt.scale || 1
+ var scale = opt.scale || 1
+ this.rotationX = opt.rotationX || 0
+ this.rotationY = opt.rotationY || 0
+ this.rotationZ = opt.rotationZ || 0
+ var width = this.width = opt.width || 100
+ var height = this.height = opt.height || 100
+ var depth = this.depth = opt.depth || 100
+ var color = this.color = opt.color || 'rgba(0, 255, 122, .1)'
+ var backgroundImage = this.backgroundImage = opt.backgroundImage;
+ var borderColor = this.borderColor = opt.borderColor || '#0f3'
+ var borderWidth = this.borderWidth = typeof opt.borderWidth !== 'undefined' ? opt.borderWidth : 3;
+ var borderRadius = this.borderRadius = typeof opt.borderRadius !== 'undefined' ? opt.borderRadius : undefined;
+ var sides = this.sides = opt.sides || "top bottom left right front back"
+ var className = this.className = opt.className || null
+
+ // an Object3D's associated DOM node is the "el" property
+ this.el.classList.add('box')
+
+ var angle = MX.rotationUnit === 'deg' ? 90 : (Math.PI / 2)
+
+ this.top = this.bottom = this.left = this.right = this.front = this.back = null
+ if (-1 != sides.indexOf("top")) {
+ var top = this.top = new MX.Object3D('.face.top')
+ top.rotationX = angle
+ top.width = width
+ top.height = depth
+ top.y = height * scale
+ top.scale = scale
+ this.add(top)
+ }
+ if (-1 != sides.indexOf("bottom")) {
+ var bottom = this.bottom = new MX.Object3D('.face.bottom')
+ bottom.rotationX = -angle
+ bottom.width = width
+ bottom.height = depth
+ bottom.y = 0
+ bottom.scale = scale
+ this.add(bottom)
+ }
+ if (-1 != sides.indexOf("left")) {
+ var left = this.left = new MX.Object3D('.face.left')
+ left.rotationY = -angle
+ left.width = depth
+ left.height = height
+ left.x = -width/2 * scale
+ left.y = height/2 * scale
+ left.scale = scale
+ this.add(left)
+ }
+ if (-1 != sides.indexOf("right")) {
+ var right = this.right = new MX.Object3D('.face.right')
+ right.rotationY = angle
+ right.width = depth
+ right.height = height
+ right.x = width/2 * scale
+ right.y = height/2 * scale
+ right.scale = scale
+ this.add(right)
+ }
+ if (-1 != sides.indexOf("front")) {
+ var front = this.front = new MX.Object3D('.face.front')
+ front.width = width
+ front.height = height
+ front.z = -depth/2 * scale
+ front.y = height/2 * scale
+ front.scale = scale
+ this.add(front)
+ }
+ if (-1 != sides.indexOf("back")) {
+ var back = this.back = new MX.Object3D('.face.back')
+ back.width = width
+ back.height = height
+ back.rotationY = angle * 2
+ back.z = depth/2 * scale
+ back.y = height/2 * scale
+ back.scale = scale
+ this.add(back)
+ }
+
+ this.children.forEach(function (face) {
+ if (borderRadius) {
+ face.el.style.borderRadius = borderRadius + "px"
+ }
+ if (className) {
+ face.el.classList.add(className)
+ }
+ else {
+ if (backgroundImage) {
+ face.el.style.backgroundImage = "url(" + backgroundImage + ")"
+ }
+ else if (color) {
+ face.el.style.backgroundColor = color
+ }
+ if (borderWidth) {
+ face.el.style.border = borderWidth + 'px solid ' + borderColor
+ }
+ }
+ })
+
+ // bottom.el.style.border = "0"
+
+ // this applies the updated CSS style
+ // required for any change to take effect
+ // when a parent object's update() is called
+ // all its children will be updated as well
+ this.update()
+
+ // if this object's children won't move by themselves
+ this.updateChildren = true
+
+ this.setWidth = function(w){
+ base.width = top.width = bottom.width = front.width = back.width = w
+ left.x = -w/2
+ right.x = w/2
+ base.dirty = true
+ }
+ this.setHeight = function(h){
+ base.height = left.height = right.height = front.height = back.height = h
+ bottom.y = 0
+ left.y = right.y = front.y = back.y = h/2
+ top.y = h
+ base.dirty = true
+ }
+ this.setDepth = function(d){
+ base.depth = top.height = bottom.height = left.width = right.width = d
+ front.z = -d/2
+ back.z = d/2
+ base.dirty = true
+ }
+
+ },
+
+ toString: function(){
+ var params = "id width height depth x y z rotationX rotationY scale color borderColor borderWidth backgroundImage borderRadius sides".split(" ")
+ return this.__toString(params)
+ },
+
+ // other properties will be mixed into the prototype of the new constructor
+
+}) \ No newline at end of file
diff --git a/assets/javascripts/mx/primitives/mx.coords.js b/assets/javascripts/mx/primitives/mx.coords.js
new file mode 100644
index 0000000..80b148c
--- /dev/null
+++ b/assets/javascripts/mx/primitives/mx.coords.js
@@ -0,0 +1,61 @@
+MX.Coords = (function () {
+
+ var colors = {
+ x: '#f33',
+ y: '#3f3',
+ z: '#66f'
+ }
+
+ var Axis = MX.Object3D.extend({
+ init: function (axis, size) {
+
+ var label = document.createElement('span')
+ label.textContent = axis.toUpperCase()
+ label.style.position = 'absolute'
+ label.style.right = '0px'
+ label.style.bottom = '3px'
+ label.style.fontSize = Math.round(size / 10) + 'px'
+ this.el.appendChild(label)
+
+ var faceA = new MX.Object3D(),
+ faceB = new MX.Object3D()
+ faceA.rotationX = 90
+ this.add(faceA, faceB)
+
+ this.el.style.color =
+ faceA.el.style.backgroundColor =
+ faceB.el.style.backgroundColor = colors[axis]
+
+ this.width =
+ faceA.width =
+ faceB.width = size
+
+ this.height =
+ faceA.height =
+ faceB.height = Math.round(size / 100)
+
+ var angle = MX.rotationUnit === 'deg' ? 90 : (Math.PI / 2)
+
+ if (axis === 'y') {
+ this.rotationZ = -angle
+ } else if (axis === 'z') {
+ this.rotationY = angle
+ }
+ }
+ })
+
+ var Coords = MX.Object3D.extend({
+ init: function (size) {
+ size = size || 100
+ var x = new Axis('x', size),
+ y = new Axis('y', size),
+ z = new Axis('z', size)
+ this.add(x, y, z)
+ this.update()
+ this.updateChildren = false
+ }
+ })
+
+ return Coords
+
+})() \ No newline at end of file
diff --git a/assets/javascripts/mx/primitives/mx.cutout.js b/assets/javascripts/mx/primitives/mx.cutout.js
new file mode 100644
index 0000000..9d9043f
--- /dev/null
+++ b/assets/javascripts/mx/primitives/mx.cutout.js
@@ -0,0 +1,66 @@
+MX.Cutout = MX.Object3D.extend({
+ init: function (ops) {
+
+ this.type = "Cutout"
+
+ var layer = this
+ layer.width = 0
+ layer.height = 0
+
+ if (ops.src) this.loadTexture(ops)
+
+ if (ops.texture) {
+ }
+ else if (ops.classname) {
+ layer.el.classList.add(ops.classname)
+ }
+ else {
+ }
+ layer.el.style.backgroundRepeat = 'no-repeat'
+
+ this.dirty = true
+ this.updateChildren = true
+ this.update()
+ },
+
+ loadTexture: function(ops){
+ var layer = this
+ var image = new Image()
+ var pattern = ops.pattern
+ var texture = ops.texture
+
+ image.onload = function(){
+ var canvas = document.createElement("canvas")
+ var ctx = canvas.getContext('2d')
+
+ layer.width = canvas.width = image.naturalWidth
+ layer.height = canvas.height = image.naturalHeight
+
+ ctx.drawImage(image, 0, 0, canvas.width, canvas.height)
+ ctx.globalCompositeOperation = "source-in"
+
+ if (texture) {
+ ctx.fillStyle = ctx.createPattern(texture, 'repeat')
+ ctx.fillRect(0, 0, canvas.width, canvas.height)
+ }
+ if (pattern) {
+ ctx.fillStyle = ctx.createPattern(pattern, 'repeat')
+ ctx.fillRect(0, 0, canvas.width, canvas.height)
+ }
+
+ layer.scale = ops.scale || 1
+ layer.x = ops.x || 0
+ layer.y = (ops.y || 0) + layer.height/2 + 1
+ layer.z = ops.z || 0
+ layer.rotationX = ops.rotationX || 0
+ layer.rotationY = ops.rotationY || 0
+ layer.el.appendChild(canvas)
+
+ layer.el.classList.add('image')
+ ops.className && layer.el.classList.add(ops.className)
+ layer.dirty = true
+ layer.update()
+ }
+ image.src = ops.src;
+ }
+})
diff --git a/assets/javascripts/mx/primitives/mx.face.js b/assets/javascripts/mx/primitives/mx.face.js
new file mode 100644
index 0000000..ac47ab4
--- /dev/null
+++ b/assets/javascripts/mx/primitives/mx.face.js
@@ -0,0 +1,41 @@
+MX.Face = MX.Object3D.extend({
+
+ // this will be called within the contructor
+ init: function (size, color, borderColor) {
+
+ size = size || 100
+ color = color || 'rgba(0, 255, 122, .1)'
+ borderColor = borderColor || '#0f3'
+
+ // an Object3D's associated DOM node is the "el" property
+ this.el.classList.add('face')
+
+ var angle = MX.rotationUnit === 'deg' ? 90 : (Math.PI / 2)
+
+ var top = this.top = new MX.Object3D('.face')
+ top.rotationX = angle
+ top.y = size / 2
+
+ // adding children, must also be instances of Object3D
+ this.add(top)
+
+ this.children.forEach(function (face) {
+ face.width = size - 2
+ face.height = size - 2
+ face.el.style.backgroundColor = color
+ face.el.style.border = '1px solid ' + borderColor
+ })
+
+ // this applies the updated CSS style
+ // required for any change to take effect
+ // when a parent object's update() is called
+ // all its children will be updated as well
+ this.update()
+
+ // if this object's children won't move by themselves
+ this.updateChildren = false
+ }
+
+ // other properties will be mixed into the prototype of the new constructor
+
+})
diff --git a/assets/javascripts/mx/primitives/mx.image.js b/assets/javascripts/mx/primitives/mx.image.js
new file mode 100644
index 0000000..cf4446e
--- /dev/null
+++ b/assets/javascripts/mx/primitives/mx.image.js
@@ -0,0 +1,49 @@
+MX.Image = MX.Object3D.extend({
+ init: function (ops) {
+
+ this.type = "Image"
+
+ var layer = this
+ layer.width = 0
+ layer.height = 0
+ layer.x = ops.x || 0
+ layer.y = ops.y || 0
+ layer.z = ops.z || 0
+
+ if (ops.src) this.loadTexture(ops)
+
+ layer.el.classList.add(ops.className)
+ layer.el.style.backgroundRepeat = 'no-repeat'
+
+ this.dirty = true
+ this.updateChildren = true
+ this.update()
+ },
+
+ loadTexture: function(ops){
+ var layer = this
+ var image = new Image()
+ image.onload = function(){
+ layer.scale = ops.scale || 1
+ layer.width = image.naturalWidth
+ layer.height = image.naturalHeight
+ layer.x = ops.x || 0
+ layer.y = (ops.y || 0) + layer.scale * layer.height/2 + 1
+ layer.z = ops.z || 0
+ layer.rotationX = ops.rotationX || 0
+ layer.rotationY = ops.rotationY || 0
+ layer.rotationZ = ops.rotationZ || 0
+ layer.el.style.backgroundImage = "url(" + image.src + ")"
+ layer.el.classList.add('image')
+ layer.dirty = true
+ layer.update()
+ }
+ image.src = ops.src;
+ },
+
+ toString: function(){
+ var params = "id src width height depth x y z rotationX rotationY rotationZ scale".split(" ")
+ return this.__toString(params)
+ },
+
+})
diff --git a/assets/javascripts/mx/primitives/mx.scaleBox.js b/assets/javascripts/mx/primitives/mx.scaleBox.js
new file mode 100644
index 0000000..77f45e9
--- /dev/null
+++ b/assets/javascripts/mx/primitives/mx.scaleBox.js
@@ -0,0 +1,140 @@
+MX.ScaleBox = MX.Object3D.extend({
+
+ // this will be called within the contructor
+ init: function (opt) {
+
+ var base = this
+
+ this.type = "ScaleBox"
+
+ var id = this.id = opt.id || guid()
+ this.x = opt.x || 0
+ this.y = opt.y || 0
+ this.z = opt.z || 0
+ this.rotationX = opt.rotationX || 0
+ this.rotationY = opt.rotationY || 0
+ this.rotationZ = opt.rotationZ || 0
+ var scale = this.scale = opt.scale || 1
+ var width = this.width = scale * (opt.width || 100)
+ var height = this.height = scale * (opt.height || 100)
+ var depth = this.depth = scale * (opt.depth || 100)
+ var color = this.color = opt.color || 'rgba(0, 255, 122, .1)'
+ var sides = this.sides = opt.sides || "top bottom left right front back"
+
+ // an Object3D's associated DOM node is the "el" property
+ this.el.classList.add('box')
+
+ var angle = MX.rotationUnit === 'deg' ? 90 : (Math.PI / 2)
+
+ var top = this.top = new MX.Object3D('.face.top')
+ top.rotationX = angle
+ top.width = 1
+ top.height = 1
+ top.scaleX = width
+ top.scaleY = 1
+ top.scaleZ = depth
+ top.y = height
+
+ var bottom = this.bottom = new MX.Object3D('.face.bottom')
+ bottom.rotationX = -angle
+ bottom.width = 1
+ bottom.height = 1
+ bottom.scaleX = width
+ bottom.scaleY = 1
+ bottom.scaleZ = depth
+ bottom.y = 0
+
+ var left = this.left = new MX.Object3D('.face.left')
+ left.rotationY = -angle
+ left.width = 1
+ left.height = 1
+ left.scaleX = 1
+ left.scaleY = height
+ left.scaleZ = depth
+ left.x = -width/2
+ left.y = height/2
+
+ var right = this.right = new MX.Object3D('.face.right')
+ right.rotationY = angle
+ right.width = 1
+ right.height = 1
+ right.scaleX = 1
+ right.scaleY = height
+ right.scaleZ = depth
+ right.x = width/2
+ right.y = height/2
+
+ var front = this.front = new MX.Object3D('.face.front')
+ front.width = 1
+ front.height = 1
+ front.scaleX = width
+ front.scaleY = height
+ front.scaleZ = 1
+ front.z = -depth/2
+ front.y = height/2
+
+ var back = this.back = new MX.Object3D('.face.back')
+ back.width = 1
+ back.height = 1
+ back.scaleX = width
+ back.scaleY = height
+ back.scaleZ = 1
+ back.rotationY = angle * 2
+ back.z = depth/2
+ back.y = height/2
+
+ // adding children, must also be instances of Object3D
+ if (-1 != sides.indexOf("top")) this.add(top)
+ if (-1 != sides.indexOf("bottom")) this.add(bottom)
+ if (-1 != sides.indexOf("left")) this.add(left)
+ if (-1 != sides.indexOf("right")) this.add(right)
+ if (-1 != sides.indexOf("front")) this.add(front)
+ if (-1 != sides.indexOf("back")) this.add(back)
+
+ this.children.forEach(function (face) {
+ face.el.style.backgroundColor = color
+ })
+
+ // this applies the updated CSS style
+ // required for any change to take effect
+ // when a parent object's update() is called
+ // all its children will be updated as well
+ this.update()
+
+ // if this object's children won't move by themselves
+ this.updateChildren = true
+
+ this.setWidth = function(w){
+ base.width = top.width = bottom.width = front.width = back.width = w
+ left.x = -w/2
+ right.x = w/2
+ base.dirty = true
+ }
+ this.setHeight = function(h){
+ base.height = left.height = right.height = front.height = back.height = h
+ bottom.y = 0
+ left.y = right.y = front.y = back.y = h/2
+ top.y = h
+ base.dirty = true
+ }
+ this.setDepth = function(d){
+ base.depth = top.height = bottom.height = left.width = right.width = d
+ front.z = -d/2
+ back.z = d/2
+ base.dirty = true
+ }
+
+ },
+
+ toString: function(){
+ var params = "id width height depth x y z rotationX rotationY color sides".split(" ")
+ return this.__toString(params)
+ },
+
+ clone: function(){
+ return new MX[this.type] (this)
+ }
+
+ // other properties will be mixed into the prototype of the new constructor
+
+})
diff --git a/assets/javascripts/mx/primitives/mx.tableau.js b/assets/javascripts/mx/primitives/mx.tableau.js
new file mode 100644
index 0000000..514e206
--- /dev/null
+++ b/assets/javascripts/mx/primitives/mx.tableau.js
@@ -0,0 +1,48 @@
+
+
+var Tableau = function(){
+ this.extend = extend.bind(Tableau)
+
+ function extend (props) {
+ var Super = this
+ var ExtendedTableau = function () {
+ Super.call(this)
+ props.init && props.init.apply(this, arguments)
+ }
+ ExtendedTableau.prototype = Object.create(Tableau.prototype)
+ for (var prop in props) {
+ if (props.hasOwnProperty(prop) && prop !== 'init') {
+ ExtendedTableau.prototype[prop] = props[prop]
+ }
+ }
+ ExtendedTableau.extend = extend.bind(ExtendedTableau)
+ return ExtendedTableau
+ }
+}
+
+Tableau.prototype.init = function(opt){}
+Tableau.prototype.animate = function(t){}
+Tableau.prototype.show = function(){}
+Tableau.prototype.hide = function(){}
+
+MX.Tableau = new Tableau()
+MX.Tableaux = {}
+
+/*
+
+MX.Tableaux.Foo = MX.Tableau.extend({
+ // this will be called within the contructor
+ init: function (opt) {
+ },
+
+ show: function(){
+ },
+
+ hide: function(){
+ },
+
+ animate: function() {
+ }
+})
+
+*/
diff --git a/assets/javascripts/mx/primitives/mx.text.js b/assets/javascripts/mx/primitives/mx.text.js
new file mode 100644
index 0000000..0c278a9
--- /dev/null
+++ b/assets/javascripts/mx/primitives/mx.text.js
@@ -0,0 +1,34 @@
+MX.Text = MX.Object3D.extend({
+ init: function (ops) {
+
+ this.type = "Text"
+
+ var layer = new MX.Object3D('text')
+ layer.width = ops.width || 100
+ layer.height = ops.height || 50
+ layer.x = ops.x || 0
+ layer.y = ops.y || 0
+ layer.z = ops.z || 0
+ layer.scale = ops.scale || 1
+ layer.el.innerHTML = ops.value || ""
+ if (ops.id) layer.el.id = ops.id;
+ if (ops.background) layer.el.style.background = ops.background;
+ if (ops.color) layer.el.style.color = ops.color;
+ if (ops.fontSize) layer.el.style.fontSize = ops.fontSize + "px";
+
+ this.add(layer)
+
+ this.children.forEach(function (c, i) {
+ if (ops.classname) {
+ c.el.classList.add(ops.classname)
+ }
+ else {
+ }
+ c.el.style.backgroundRepeat = 'no-repeat'
+ })
+
+ this.dirty = true
+ this.updateChildren = true
+ this.update()
+ }
+})
diff --git a/assets/javascripts/mx/primitives/mx.texturedBox.js b/assets/javascripts/mx/primitives/mx.texturedBox.js
new file mode 100644
index 0000000..daec2d8
--- /dev/null
+++ b/assets/javascripts/mx/primitives/mx.texturedBox.js
@@ -0,0 +1,121 @@
+// Creates a box using a given texture image.
+// Uses a texture image like this:
+//
+// ---------- ----------
+// | | |
+// | top | bottom |
+// | | |
+// ---------- ---------- ---------- ----------
+// | | | | |
+// | left | front | right | back |
+// | | | | |
+// ---------- ---------- ---------- ----------
+//
+// See `examples/images/skins/` for some minecraft skin examples.
+
+// Options:
+//
+// - {number} `width`
+// - {number} `height`
+// - {number} `depth`
+// - {string} `texture` path to texture image
+// - {string} `classname` class to be added to dom element
+
+MX.TexturedBox = MX.Object3D.extend({
+
+ init: function (ops) {
+
+ this.type = "TexturedBox"
+
+ if (!ops.width || !ops.height || !ops.depth || (!ops.texture && !ops.classname)) {
+ console.warn('TextureBox: missing arguments')
+ return
+ }
+
+ // faces
+ var angle = MX.rotationUnit === 'deg' ? 90 : (Math.PI / 2),
+ offsetX = ops.offset ? (ops.offset.x || 0) : 0,
+ offsetY = ops.offset ? (ops.offset.y || 0) : 0,
+ overlap = ops.overlap ? ops.overlap : 0
+ var multiTexture = typeof ops.texture == "object";
+
+ var top = this.top = new MX.Object3D()
+ top.width = ops.width
+ top.height = ops.depth
+ top.rotationX = angle
+ top.y = ops.height / 2 - overlap
+ if (!multiTexture)
+ top.el.style.backgroundPosition =
+ (-(offsetX + ops.depth) + 'px ') +
+ (-offsetY + 'px')
+
+ var bottom = this.bottom = new MX.Object3D()
+ bottom.width = ops.width
+ bottom.height = ops.depth
+ bottom.rotationX = -angle
+ bottom.y = -ops.height / 2 + overlap
+ if (!multiTexture)
+ bottom.el.style.backgroundPosition =
+ (-(offsetX + ops.depth + ops.width) + 'px ') +
+ (-offsetY + 'px')
+
+ var left = this.left = new MX.Object3D()
+ left.width = ops.depth
+ left.height = ops.height
+ left.rotationY = -angle
+ left.x = -ops.width / 2 + overlap
+ if (!multiTexture)
+ left.el.style.backgroundPosition =
+ (-offsetX + 'px ') +
+ (-(offsetY + ops.depth) + 'px')
+
+ var right = this.right = new MX.Object3D()
+ right.width = ops.depth
+ right.height = ops.height
+ right.rotationY = angle
+ right.x = ops.width / 2 - overlap
+ if (!multiTexture)
+ right.el.style.backgroundPosition =
+ (-(offsetX + ops.depth + ops.width) + 'px ') +
+ (-(offsetY + ops.depth) + 'px')
+
+ var front = this.front = new MX.Object3D()
+ front.width = ops.width
+ front.height = ops.height
+ front.z = -ops.depth / 2 + overlap
+ if (!multiTexture)
+ front.el.style.backgroundPosition =
+ (-(offsetX + ops.depth) + 'px ') +
+ (-(offsetY + ops.depth) + 'px')
+
+ var back = this.back = new MX.Object3D()
+ back.width = ops.width
+ back.height = ops.height
+ back.rotationY = angle * 2
+ back.z = ops.depth / 2 - overlap
+ if (!multiTexture)
+ back.el.style.backgroundPosition =
+ (-(offsetX + ops.depth * 2 + ops.width) + 'px ') +
+ (-(offsetY + ops.depth) + 'px')
+
+ this.add(top, bottom, left, right, front, back)
+
+ this.children.forEach(function (c,i) {
+ if (multiTexture) {
+ c.el.style.backgroundImage = 'url(' + ops.texture[i] + ')'
+ }
+ else if (ops.texture) {
+ c.el.style.backgroundImage = 'url(' + ops.texture + ')'
+ }
+ if (ops.classname) {
+ c.el.classList.add(ops.classname)
+ }
+ c.el.style.backgroundRepeat = 'no-repeat'
+ })
+
+ this.update()
+ this.updateChildren = false
+
+ }
+
+})
diff --git a/assets/javascripts/util.js b/assets/javascripts/util.js
new file mode 100644
index 0000000..d100731
--- /dev/null
+++ b/assets/javascripts/util.js
@@ -0,0 +1,165 @@
+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") }
+}
+
+function trim(s){ return s.replace(/^\s+/,"").replace(/\s+$/,"") }
+
+var E = Math.E
+var PI = Math.PI
+var PHI = (1+Math.sqrt(5))/2
+var TWO_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 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 Math.abs(n)/n }
+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){
+ for (var i = a.length; i > 0; i--){
+ var r = randint(i)
+ var 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
+}
+
+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() {
+ 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);
+ };
+}());
diff --git a/assets/javascripts/vendor/loader.js b/assets/javascripts/vendor/loader.js
new file mode 100644
index 0000000..4c1c8cd
--- /dev/null
+++ b/assets/javascripts/vendor/loader.js
@@ -0,0 +1,68 @@
+var Loader = Loader || (function(){
+ function Loader (readyCallback){
+ this.assets = {};
+ this.images = [];
+ this.readyCallback = readyCallback;
+ }
+
+ // Register an asset as loading
+ Loader.prototype.register = function(s){
+ this.assets[s] = false;
+ }
+
+ // Signal that an asset has loaded
+ Loader.prototype.ready = function(s){
+ window.debug && console.log("ready >> " + s);
+
+ this.assets[s] = true;
+ if (this.loaded) return;
+ if (! this.isReady()) return;
+
+ this.loaded = true;
+ this.readyCallback && this.readyCallback();
+ }
+
+ // (boolean) Is the loader ready?
+ Loader.prototype.isReady = function(){
+ for (var s in this.assets) {
+ if (this.assets.hasOwnProperty(s) && this.assets[s] != true) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // (int) Number of assets remaining
+ Loader.prototype.remainingAssets = function(){
+ var n = 0;
+ for (var s in this.assets) {
+ if (this.assets.hasOwnProperty(s) && this.assets[s] != true) {
+ n++;
+ console.log('remaining: ' + s);
+ }
+ }
+ return n;
+ }
+
+ // Preload the images in config.images
+ Loader.prototype.preloadImages = function(images){
+ this.register("preload");
+ for (var i = 0; i < images.length; i++) {
+ this.preloadImage(images[i]);
+ }
+ this.ready("preload");
+ }
+ Loader.prototype.preloadImage = function(src){
+ var _this = this;
+ this.register(src);
+ var img = new Image();
+ img.onload = function(){
+ _this.ready(src);
+ }
+ img.src = src;
+ if (img.complete) img.onload();
+ _this.images.push(img);
+ }
+
+ return Loader;
+})();
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;
+})()
+
diff --git a/assets/javascripts/vendor/tween.js b/assets/javascripts/vendor/tween.js
new file mode 100644
index 0000000..b246ead
--- /dev/null
+++ b/assets/javascripts/vendor/tween.js
@@ -0,0 +1,741 @@
+/**
+ * @author sole / http://soledadpenades.com
+ * @author mrdoob / http://mrdoob.com
+ * @author Robert Eisele / http://www.xarg.org
+ * @author Philippe / http://philippe.elsass.me
+ * @author Robert Penner / http://www.robertpenner.com/easing_terms_of_use.html
+ * @author Paul Lewis / http://www.aerotwist.com/
+ * @author lechecacharro
+ * @author Josh Faul / http://jocafa.com/
+ * @author egraether / http://egraether.com/
+ * @author endel / http://endel.me
+ * @author Ben Delarre / http://delarre.net
+ */
+
+// Date.now shim for (ahem) Internet Explo(d|r)er
+if ( Date.now === undefined ) {
+
+ Date.now = function () {
+
+ return new Date().valueOf();
+
+ };
+
+}
+
+var TWEEN = TWEEN || ( function () {
+
+ var _tweens = [];
+
+ return {
+
+ REVISION: '12',
+
+ getAll: function () {
+
+ return _tweens;
+
+ },
+
+ removeAll: function () {
+
+ _tweens = [];
+
+ },
+
+ add: function ( tween ) {
+
+ _tweens.push( tween );
+
+ },
+
+ remove: function ( tween ) {
+
+ var i = _tweens.indexOf( tween );
+
+ if ( i !== -1 ) {
+
+ _tweens.splice( i, 1 );
+
+ }
+
+ },
+
+ update: function ( time ) {
+
+ if ( _tweens.length === 0 ) return false;
+
+ var i = 0;
+
+ time = time !== undefined ? time : ( typeof window !== 'undefined' && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() );
+
+ while ( i < _tweens.length ) {
+
+ if ( _tweens[ i ].update( time ) ) {
+
+ i++;
+
+ } else {
+
+ _tweens.splice( i, 1 );
+
+ }
+
+ }
+
+ return true;
+
+ }
+ };
+
+} )();
+
+TWEEN.Tween = function ( object ) {
+
+ var _object = object;
+ var _valuesStart = {};
+ var _valuesEnd = {};
+ var _valuesStartRepeat = {};
+ var _duration = 1000;
+ var _repeat = 0;
+ var _yoyo = false;
+ var _isPlaying = false;
+ var _reversed = false;
+ var _delayTime = 0;
+ var _startTime = null;
+ var _easingFunction = TWEEN.Easing.Linear.None;
+ var _interpolationFunction = TWEEN.Interpolation.Linear;
+ var _chainedTweens = [];
+ var _onStartCallback = null;
+ var _onStartCallbackFired = false;
+ var _onUpdateCallback = null;
+ var _onCompleteCallback = null;
+
+ // Set all starting values present on the target object
+ for ( var field in object ) {
+
+ _valuesStart[ field ] = parseFloat(object[field], 10);
+
+ }
+
+ this.to = function ( properties, duration ) {
+
+ if ( duration !== undefined ) {
+
+ _duration = duration;
+
+ }
+
+ _valuesEnd = properties;
+
+ return this;
+
+ };
+
+ this.start = function ( time ) {
+
+ TWEEN.add( this );
+
+ _isPlaying = true;
+
+ _onStartCallbackFired = false;
+
+ _startTime = time !== undefined ? time : ( typeof window !== 'undefined' && window.performance !== undefined && window.performance.now !== undefined ? window.performance.now() : Date.now() );
+ _startTime += _delayTime;
+
+ for ( var property in _valuesEnd ) {
+
+ // check if an Array was provided as property value
+ if ( _valuesEnd[ property ] instanceof Array ) {
+
+ if ( _valuesEnd[ property ].length === 0 ) {
+
+ continue;
+
+ }
+
+ // create a local copy of the Array with the start value at the front
+ _valuesEnd[ property ] = [ _object[ property ] ].concat( _valuesEnd[ property ] );
+
+ }
+
+ _valuesStart[ property ] = _object[ property ];
+
+ if( ( _valuesStart[ property ] instanceof Array ) === false ) {
+ _valuesStart[ property ] *= 1.0; // Ensures we're using numbers, not strings
+ }
+
+ _valuesStartRepeat[ property ] = _valuesStart[ property ] || 0;
+
+ }
+
+ return this;
+
+ };
+
+ this.stop = function () {
+
+ if ( !_isPlaying ) {
+ return this;
+ }
+
+ TWEEN.remove( this );
+ _isPlaying = false;
+ this.stopChainedTweens();
+ return this;
+
+ };
+
+ this.stopChainedTweens = function () {
+
+ for ( var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++ ) {
+
+ _chainedTweens[ i ].stop();
+
+ }
+
+ };
+
+ this.delay = function ( amount ) {
+
+ _delayTime = amount;
+ return this;
+
+ };
+
+ this.repeat = function ( times ) {
+
+ _repeat = times;
+ return this;
+
+ };
+
+ this.yoyo = function( yoyo ) {
+
+ _yoyo = yoyo;
+ return this;
+
+ };
+
+
+ this.easing = function ( easing ) {
+
+ _easingFunction = easing;
+ return this;
+
+ };
+
+ this.interpolation = function ( interpolation ) {
+
+ _interpolationFunction = interpolation;
+ return this;
+
+ };
+
+ this.chain = function () {
+
+ _chainedTweens = arguments;
+ return this;
+
+ };
+
+ this.onStart = function ( callback ) {
+
+ _onStartCallback = callback;
+ return this;
+
+ };
+
+ this.onUpdate = function ( callback ) {
+
+ _onUpdateCallback = callback;
+ return this;
+
+ };
+
+ this.onComplete = function ( callback ) {
+
+ _onCompleteCallback = callback;
+ return this;
+
+ };
+
+ this.update = function ( time ) {
+
+ var property;
+
+ if ( time < _startTime ) {
+
+ return true;
+
+ }
+
+ if ( _onStartCallbackFired === false ) {
+
+ if ( _onStartCallback !== null ) {
+
+ _onStartCallback.call( _object );
+
+ }
+
+ _onStartCallbackFired = true;
+
+ }
+
+ var elapsed = ( time - _startTime ) / _duration;
+ elapsed = elapsed > 1 ? 1 : elapsed;
+
+ var value = _easingFunction( elapsed );
+
+ for ( property in _valuesEnd ) {
+
+ var start = _valuesStart[ property ] || 0;
+ var end = _valuesEnd[ property ];
+
+ if ( end instanceof Array ) {
+
+ _object[ property ] = _interpolationFunction( end, value );
+
+ } else {
+
+ // Parses relative end values with start as base (e.g.: +10, -3)
+ if ( typeof(end) === "string" ) {
+ end = start + parseFloat(end, 10);
+ }
+
+ // protect against non numeric properties.
+ if ( typeof(end) === "number" ) {
+ _object[ property ] = start + ( end - start ) * value;
+ }
+
+ }
+
+ }
+
+ if ( _onUpdateCallback !== null ) {
+
+ _onUpdateCallback.call( _object, value );
+
+ }
+
+ if ( elapsed == 1 ) {
+
+ if ( _repeat > 0 ) {
+
+ if( isFinite( _repeat ) ) {
+ _repeat--;
+ }
+
+ // reassign starting values, restart by making startTime = now
+ for( property in _valuesStartRepeat ) {
+
+ if ( typeof( _valuesEnd[ property ] ) === "string" ) {
+ _valuesStartRepeat[ property ] = _valuesStartRepeat[ property ] + parseFloat(_valuesEnd[ property ], 10);
+ }
+
+ if (_yoyo) {
+ var tmp = _valuesStartRepeat[ property ];
+ _valuesStartRepeat[ property ] = _valuesEnd[ property ];
+ _valuesEnd[ property ] = tmp;
+ _reversed = !_reversed;
+ }
+ _valuesStart[ property ] = _valuesStartRepeat[ property ];
+
+ }
+
+ _startTime = time + _delayTime;
+
+ return true;
+
+ } else {
+
+ if ( _onCompleteCallback !== null ) {
+
+ _onCompleteCallback.call( _object );
+
+ }
+
+ for ( var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++ ) {
+
+ _chainedTweens[ i ].start( time );
+
+ }
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ };
+
+};
+
+
+TWEEN.Easing = {
+
+ Linear: {
+
+ None: function ( k ) {
+
+ return k;
+
+ }
+
+ },
+
+ Quadratic: {
+
+ In: function ( k ) {
+
+ return k * k;
+
+ },
+
+ Out: function ( k ) {
+
+ return k * ( 2 - k );
+
+ },
+
+ InOut: function ( k ) {
+
+ if ( ( k *= 2 ) < 1 ) return 0.5 * k * k;
+ return - 0.5 * ( --k * ( k - 2 ) - 1 );
+
+ }
+
+ },
+
+ Cubic: {
+
+ In: function ( k ) {
+
+ return k * k * k;
+
+ },
+
+ Out: function ( k ) {
+
+ return --k * k * k + 1;
+
+ },
+
+ InOut: function ( k ) {
+
+ if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k;
+ return 0.5 * ( ( k -= 2 ) * k * k + 2 );
+
+ }
+
+ },
+
+ Quartic: {
+
+ In: function ( k ) {
+
+ return k * k * k * k;
+
+ },
+
+ Out: function ( k ) {
+
+ return 1 - ( --k * k * k * k );
+
+ },
+
+ InOut: function ( k ) {
+
+ if ( ( k *= 2 ) < 1) return 0.5 * k * k * k * k;
+ return - 0.5 * ( ( k -= 2 ) * k * k * k - 2 );
+
+ }
+
+ },
+
+ Quintic: {
+
+ In: function ( k ) {
+
+ return k * k * k * k * k;
+
+ },
+
+ Out: function ( k ) {
+
+ return --k * k * k * k * k + 1;
+
+ },
+
+ InOut: function ( k ) {
+
+ if ( ( k *= 2 ) < 1 ) return 0.5 * k * k * k * k * k;
+ return 0.5 * ( ( k -= 2 ) * k * k * k * k + 2 );
+
+ }
+
+ },
+
+ Sinusoidal: {
+
+ In: function ( k ) {
+
+ return 1 - Math.cos( k * Math.PI / 2 );
+
+ },
+
+ Out: function ( k ) {
+
+ return Math.sin( k * Math.PI / 2 );
+
+ },
+
+ InOut: function ( k ) {
+
+ return 0.5 * ( 1 - Math.cos( Math.PI * k ) );
+
+ }
+
+ },
+
+ Exponential: {
+
+ In: function ( k ) {
+
+ return k === 0 ? 0 : Math.pow( 1024, k - 1 );
+
+ },
+
+ Out: function ( k ) {
+
+ return k === 1 ? 1 : 1 - Math.pow( 2, - 10 * k );
+
+ },
+
+ InOut: function ( k ) {
+
+ if ( k === 0 ) return 0;
+ if ( k === 1 ) return 1;
+ if ( ( k *= 2 ) < 1 ) return 0.5 * Math.pow( 1024, k - 1 );
+ return 0.5 * ( - Math.pow( 2, - 10 * ( k - 1 ) ) + 2 );
+
+ }
+
+ },
+
+ Circular: {
+
+ In: function ( k ) {
+
+ return 1 - Math.sqrt( 1 - k * k );
+
+ },
+
+ Out: function ( k ) {
+
+ return Math.sqrt( 1 - ( --k * k ) );
+
+ },
+
+ InOut: function ( k ) {
+
+ if ( ( k *= 2 ) < 1) return - 0.5 * ( Math.sqrt( 1 - k * k) - 1);
+ return 0.5 * ( Math.sqrt( 1 - ( k -= 2) * k) + 1);
+
+ }
+
+ },
+
+ Elastic: {
+
+ In: function ( k ) {
+
+ var s, a = 0.1, p = 0.4;
+ if ( k === 0 ) return 0;
+ if ( k === 1 ) return 1;
+ if ( !a || a < 1 ) { a = 1; s = p / 4; }
+ else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
+ return - ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) );
+
+ },
+
+ Out: function ( k ) {
+
+ var s, a = 0.1, p = 0.4;
+ if ( k === 0 ) return 0;
+ if ( k === 1 ) return 1;
+ if ( !a || a < 1 ) { a = 1; s = p / 4; }
+ else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
+ return ( a * Math.pow( 2, - 10 * k) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) + 1 );
+
+ },
+
+ InOut: function ( k ) {
+
+ var s, a = 0.1, p = 0.4;
+ if ( k === 0 ) return 0;
+ if ( k === 1 ) return 1;
+ if ( !a || a < 1 ) { a = 1; s = p / 4; }
+ else s = p * Math.asin( 1 / a ) / ( 2 * Math.PI );
+ if ( ( k *= 2 ) < 1 ) return - 0.5 * ( a * Math.pow( 2, 10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) );
+ return a * Math.pow( 2, -10 * ( k -= 1 ) ) * Math.sin( ( k - s ) * ( 2 * Math.PI ) / p ) * 0.5 + 1;
+
+ }
+
+ },
+
+ Back: {
+
+ In: function ( k ) {
+
+ var s = 1.70158;
+ return k * k * ( ( s + 1 ) * k - s );
+
+ },
+
+ Out: function ( k ) {
+
+ var s = 1.70158;
+ return --k * k * ( ( s + 1 ) * k + s ) + 1;
+
+ },
+
+ InOut: function ( k ) {
+
+ var s = 1.70158 * 1.525;
+ if ( ( k *= 2 ) < 1 ) return 0.5 * ( k * k * ( ( s + 1 ) * k - s ) );
+ return 0.5 * ( ( k -= 2 ) * k * ( ( s + 1 ) * k + s ) + 2 );
+
+ }
+
+ },
+
+ Bounce: {
+
+ In: function ( k ) {
+
+ return 1 - TWEEN.Easing.Bounce.Out( 1 - k );
+
+ },
+
+ Out: function ( k ) {
+
+ if ( k < ( 1 / 2.75 ) ) {
+
+ return 7.5625 * k * k;
+
+ } else if ( k < ( 2 / 2.75 ) ) {
+
+ return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;
+
+ } else if ( k < ( 2.5 / 2.75 ) ) {
+
+ return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;
+
+ } else {
+
+ return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;
+
+ }
+
+ },
+
+ InOut: function ( k ) {
+
+ if ( k < 0.5 ) return TWEEN.Easing.Bounce.In( k * 2 ) * 0.5;
+ return TWEEN.Easing.Bounce.Out( k * 2 - 1 ) * 0.5 + 0.5;
+
+ }
+
+ }
+
+};
+
+TWEEN.Interpolation = {
+
+ Linear: function ( v, k ) {
+
+ var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = TWEEN.Interpolation.Utils.Linear;
+
+ if ( k < 0 ) return fn( v[ 0 ], v[ 1 ], f );
+ if ( k > 1 ) return fn( v[ m ], v[ m - 1 ], m - f );
+
+ return fn( v[ i ], v[ i + 1 > m ? m : i + 1 ], f - i );
+
+ },
+
+ Bezier: function ( v, k ) {
+
+ var b = 0, n = v.length - 1, pw = Math.pow, bn = TWEEN.Interpolation.Utils.Bernstein, i;
+
+ for ( i = 0; i <= n; i++ ) {
+ b += pw( 1 - k, n - i ) * pw( k, i ) * v[ i ] * bn( n, i );
+ }
+
+ return b;
+
+ },
+
+ CatmullRom: function ( v, k ) {
+
+ var m = v.length - 1, f = m * k, i = Math.floor( f ), fn = TWEEN.Interpolation.Utils.CatmullRom;
+
+ if ( v[ 0 ] === v[ m ] ) {
+
+ if ( k < 0 ) i = Math.floor( f = m * ( 1 + k ) );
+
+ return fn( v[ ( i - 1 + m ) % m ], v[ i ], v[ ( i + 1 ) % m ], v[ ( i + 2 ) % m ], f - i );
+
+ } else {
+
+ if ( k < 0 ) return v[ 0 ] - ( fn( v[ 0 ], v[ 0 ], v[ 1 ], v[ 1 ], -f ) - v[ 0 ] );
+ if ( k > 1 ) return v[ m ] - ( fn( v[ m ], v[ m ], v[ m - 1 ], v[ m - 1 ], f - m ) - v[ m ] );
+
+ return fn( v[ i ? i - 1 : 0 ], v[ i ], v[ m < i + 1 ? m : i + 1 ], v[ m < i + 2 ? m : i + 2 ], f - i );
+
+ }
+
+ },
+
+ Utils: {
+
+ Linear: function ( p0, p1, t ) {
+
+ return ( p1 - p0 ) * t + p0;
+
+ },
+
+ Bernstein: function ( n , i ) {
+
+ var fc = TWEEN.Interpolation.Utils.Factorial;
+ return fc( n ) / fc( i ) / fc( n - i );
+
+ },
+
+ Factorial: ( function () {
+
+ var a = [ 1 ];
+
+ return function ( n ) {
+
+ var s = 1, i;
+ if ( a[ n ] ) return a[ n ];
+ for ( i = n; i > 1; i-- ) s *= i;
+ return a[ n ] = s;
+
+ };
+
+ } )(),
+
+ CatmullRom: function ( p0, p1, p2, p3, t ) {
+
+ var v0 = ( p2 - p0 ) * 0.5, v1 = ( p3 - p1 ) * 0.5, t2 = t * t, t3 = t * t2;
+ return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+ }
+
+ }
+
+};