diff options
| author | Jules Laplace <jules@okfoc.us> | 2014-04-09 17:45:32 -0400 |
|---|---|---|
| committer | Jules Laplace <jules@okfoc.us> | 2014-04-09 17:45:32 -0400 |
| commit | cbd5e2411cd4df39dda75723da9c5e0153ad3331 (patch) | |
| tree | 61090416889211be2b0d2a37cf5fe0fd545a77e7 | |
| parent | 12d7bd4b9dba1c9e51b80059af650ff3fbfda0ca (diff) | |
box-drawing example
34 files changed, 5101 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; + + } + + } + +}; diff --git a/assets/stylesheets/css.css b/assets/stylesheets/css.css new file mode 100644 index 0000000..e536b4c --- /dev/null +++ b/assets/stylesheets/css.css @@ -0,0 +1,32 @@ +* { + margin:0; + padding:0; + outline:0; + moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; +} +html, body { + width: 100%; + height: 100%; +} +.mx-scene { + z-index: 1; + position: fixed; + top: 50%; + left: 50%; + -webkit-transform: translate3d(-50%, -50%, 0); + -moz-transform: translate3d(-50%, -50%, 0); + transform: translate3d(-50%, -50%, 0); + cursor:pointer; +} +.mx-object3d.image, .mx-object3d canvas, +.mx-object3d.backface-hidden { + -webkit-backface-visibility: hidden; + -moz-backface-visibility: hidden; + -ms-backface-visibility: hidden; +} +.mx-object3d.backface-visible, .mx-object3d.backface-visible canvas{ + -webkit-backface-visibility: visible; + -moz-backface-visibility: visible; + -ms-backface-visibility: visible; +} + diff --git a/assets/stylesheets/map.css b/assets/stylesheets/map.css new file mode 100644 index 0000000..2968f34 --- /dev/null +++ b/assets/stylesheets/map.css @@ -0,0 +1,30 @@ +#map { + z-index: 2; + display: none; + position: fixed; + bottom: 100px; + left: 50px; + width: 20%; + height: 50%; + background: white; + box-shadow: 0 10px 15px rgba(0,0,0,0.5); +} +#map .el { +} +#map canvas { + display: block; +} +#map .hud { + position: absolute; + bottom: 5px; + left: 5px; + background: rgba(255,255,255,0.8); + color: #333; +} +#map textarea { + width: 100%; + height: 75px; + border: 0; + border-top: 1px dashed #bbf; + box-shadow: 0 0 15px rgba(80,80,80,0.8); +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..c695394 --- /dev/null +++ b/index.html @@ -0,0 +1,48 @@ +<!DOCTYPE html> +<html> +<head> + <title></title> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> + <link rel="stylesheet" type="text/css" href="assets/stylesheets/css.css"> + <link rel="stylesheet" type="text/css" href="assets/stylesheets/map.css"> +</head> +<body> + <div id="bg"></div> + <div id="scene"></div> + + <div id="map" style="display: block"> + <span class="el"></span> + <span class="hud"></span> + <textarea id="export"></textarea> + </div> + + <div id="minimap" class="box"> + <span class="el"></span> + </div> + +<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> +<script src="assets/javascripts/vendor/tween.js"></script> +<script src="assets/javascripts/vendor/tube.js"></script> +<script src="assets/javascripts/vendor/loader.js"></script> +<script src="assets/javascripts/mx/mx.js"></script> +<script src="assets/javascripts/mx/mx.quaternion.js"></script> +<script src="assets/javascripts/mx/mx.minimap.js"></script> +<script src="assets/javascripts/mx/extensions/mx.scene.js"></script> +<script src="assets/javascripts/mx/extensions/mx.movements.js"></script> +<script src="assets/javascripts/mx/extensions/mx.rotationControl.js"></script> +<script src="assets/javascripts/mx/extensions/mx.scrubber.js"></script> +<script src="assets/javascripts/mx/primitives/mx.face.js"></script> +<script src="assets/javascripts/mx/primitives/mx.box.js"></script> +<script src="assets/javascripts/mx/primitives/mx.scaleBox.js"></script> +<script src="assets/javascripts/mx/primitives/mx.boxDimensions.js"></script> +<script src="assets/javascripts/mx/primitives/mx.image.js"></script> +<script src="assets/javascripts/mx/primitives/mx.cutout.js"></script> +<script src="assets/javascripts/mx/primitives/mx.text.js"></script> +<script src="assets/javascripts/mx/primitives/mx.tableau.js"></script> +<script src="assets/javascripts/environments/app.js"></script> +<script src="assets/javascripts/map/map.js"></script> +<script src="assets/javascripts/map/map_editor.js"></script> +<script src="assets/javascripts/app.js"></script> +<script src="assets/javascripts/util.js"></script> +</body> +</html> diff --git a/package.json b/package.json new file mode 100644 index 0000000..47528f9 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "vvalls", + "version": "1.0.0", + "repository": { + "type": "git", + "url": "git://github.com/okfocus/vvalls.git" + }, + "devDependencies": { + "grunt": "~0.4.1", + "grunt-contrib-concat": "~0.3.0", + "grunt-contrib-uglify": "~0.2.5", + "grunt-contrib-watch": "~0.5.3", + "grunt-contrib-clean": "~0.5.0", + "grunt-contrib-copy": "~0.5.0", + "grunt-dentist": "~0.3.4" + } +} |
