diff options
Diffstat (limited to 'site/public/assets/javascripts/mx')
17 files changed, 3777 insertions, 0 deletions
diff --git a/site/public/assets/javascripts/mx/extensions/mx.movements.js b/site/public/assets/javascripts/mx/extensions/mx.movements.js new file mode 100644 index 0000000..9af2c8d --- /dev/null +++ b/site/public/assets/javascripts/mx/extensions/mx.movements.js @@ -0,0 +1,367 @@ + + +MX.Movements = function (cam) { + + var moveForward, + moveLeft, + moveBackward, + moveRight, + moveUp, + moveDown, + turnLeft, + turnRight, + turnUp, + turnDown, + jumping = false, + creeping = false, + locked = false, + gravity = false, + rotationX_min = PI/-4, + rotationX_max = PI/6 + + var v = 12, + vr = Math.PI * 0.012, + jumpV = 23, + vx = vy = vz = 0, + creepFactor = 0.3 + + var mouseX, mouseY, dx, dy, rotX, rotY, dragging = false + + var trackpad + + var DEFAULT_SCALE = 1.0, scale = DEFAULT_SCALE + + var pos = { x: 0, y: 0, z: 0, rotationX: 0, rotationY: 0 } + + $(document).one("keydown", function(){ + $("#keyhint").fadeOut(250); + $('.reader #minimap').addClass('active'); + }) + + function clampRotation( vr ) { + if (Rooms.mover.noclip) { + return clamp(vr, PI/-2, PI/2 ) + } + else { + return clamp(vr, PI/-4, PI/6 ) + } + } + + var exports = { + + init: function () { + + trackpad = new wheel ({ + el: scene.el, + update: exports.mousewheel, + }) + + document.addEventListener('keydown', exports.keydown) + document.addEventListener('keyup', exports.keyup) + document.addEventListener('mousedown', exports.mousedown) + document.addEventListener('mousemove', exports.mousemove) + document.addEventListener('mouseup', exports.mouseup) + window.addEventListener('blur', exports.reset) + window.addEventListener('focus', exports.reset) + }, + + keydown: function (e) { + // console.log(e.keyCode) + if (locked || e.altKey || e.metaKey || e.ctrlKey) { + return + } + switch ( e.keyCode ) { + + case 16: // shift + creeping = true + break + + case 38: // up + case 87: // w + moveForward = true + break + + case 65: // a + moveLeft = true + break + + case 40: // down + case 83: // s + moveBackward = true + break + + case 68: // d + moveRight = true + break + + case 37: // left + case 81: // q + turnLeft = true + break + + case 39: // right + 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 + if (Scenery.nextMedia) { + Scenery.nextMedia = null + app.tube('cancel-scenery') + } + else if (Scenery.nextWallpaper) { + Scenery.nextWallpaper = null + app.tube('cancel-wallpaper') + } + else if (app.controller.mediaViewer && app.controller.mediaViewer.$el.hasClass("active")) { + app.controller.mediaViewer.hide() + $(".inuse").removeClass("inuse") + } + else if (app.controller.colorControl && app.controller.colorControl.$el.hasClass('active')) { + app.controller.colorControl.hide() + $(".inuse").removeClass("inuse") + } + else if (app.controller.wallpaperPicker && app.controller.wallpaperPicker.$el.hasClass('active')) { + app.controller.wallpaperPicker.hide() + $(".inuse").removeClass("inuse") + } + else if (app.controller.presets && app.controller.presets.$el.hasClass('active')) { + app.controller.presets.hide() + $(".inuse").removeClass("inuse") + } + else { + app.controller.toolbar.toggleMap() + } + break + + case 8: // backspace + e.preventDefault() + if (app.controller.mediaEditor.scenery) { + app.controller.mediaEditor.scenery.remove() + } + else if (app.controller.textEditor.scenery) { + app.controller.textEditor.scenery.remove() + } + } + }, + + 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 65: // a + moveLeft = false + break + + case 40: // down + case 83: // s + moveBackward = false + break + + case 68: // d + moveRight = false + break + + case 37: // left + case 81: // q + turnLeft = false + break + + case 39: // right + 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 +*/ + } + }, + + mousedown: function (e) { + if (locked) return; + mouseX = e.pageX + mouseY = e.pageY + rotX = cam.rotationX + rotY = cam.rotationY + dragging = true + }, + + mousemove: function (e) { + if (locked || ! 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 = clampRotation( rotX - dy ) + }, + + mouseup: function (e) { + dragging = false + }, + + reset: function(){ + moveForward = moveLeft = moveBackward = moveRight = moveUp = moveDown = turnLeft = turnRight = jumping = dragging = creeping = false + }, + + mousewheel: function (e, deltaY, deltaX) { + if (e.shiftKey) { + cam.rotationY -= deltaY / 150 + } + else { + pos.x += deltaY * Math.cos(cam.rotationY + Math.PI / 2) * 10 + pos.z += deltaY * Math.sin(cam.rotationY + Math.PI / 2) * 10 + app.tube("move", pos) + } + }, + + update: function (dt) { + + if (locked) { return } + + var ry = cam.rotationY + var s = creeping ? scale * creepFactor : scale + var vrrrr = creeping ? vr * creepFactor * 5 : vr * 0.5 + var moving = moveForward || moveBackward || moveRight || moveLeft || moveUp || moveDown || turnLeft || turnRight || turnUp || turnDown + vx = vz = 0 + + var vv = v +// vv *= dt / 100 * 8 +// s *= dt / 100 * 8 +// console.log(dt / 100 * 8) + + pos.x = cam.x + pos.z = cam.z + + if (moving) { + + if (moveForward) { + vx += vv * Math.cos(ry + Math.PI / 2) * s + vz += vv * Math.sin(ry + Math.PI / 2) * s + } + if (moveBackward) { + vx -= vv * Math.cos(ry + Math.PI / 2) * s + vz -= vv * Math.sin(ry + Math.PI / 2) * s + } + if (moveLeft) { + vx -= vv * Math.cos(ry) * s + vz -= vv * Math.sin(ry) * s + } + if (moveRight) { + vx += vv * Math.cos(ry) * s + vz += vv * Math.sin(ry) * s + } + if (moveUp) { + pos.y += vv * scale + } + if (moveDown) { + pos.y -= vv * scale + } + + if (turnUp) { + cam.rotationX = clampRotation( cam.rotationX - vrrrr*s ) + } + if (turnDown) { + cam.rotationX = clampRotation( cam.rotationX + vrrrr*s ) + } + if (turnLeft) { + cam.rotationY += vrrrr*s + } + if (turnRight) { + cam.rotationY -= vrrrr*s + } + + pos.x += vx + pos.z += vz + } + + if (gravity) { + vy -= 1 * scale + + pos.y += vy + + if (vy) { + moving = true + } + if (pos.y <= viewHeight) { + pos.y = viewHeight + vy = 0 + jumping = false + } + + var ceiling = (Rooms.mover.room ? Rooms.mover.room.height : 5000) + + if (pos.y >= ceiling-5) { + vy = 0 + pos.y = ceiling-5 + } + } + + if (moving) { + app.tube("move", pos) + } + }, + + lock: function(){ locked = true }, + unlock: function(){ locked = false }, + scale: function(n){ if (n) scale = n; return scale }, + resetScale: function(n){ scale = DEFAULT_SCALE }, + gravity: function(g){ return typeof g == "boolean" ? gravity = g : gravity }, + velocity: function(n){ v = clamp(n, 1, 50) }, + jumpVelocity: function(n){ jumpV = clamp(n, 1, 50) }, + } + + return exports +} diff --git a/site/public/assets/javascripts/mx/extensions/mx.movementsMobile.js b/site/public/assets/javascripts/mx/extensions/mx.movementsMobile.js new file mode 100644 index 0000000..95b61d1 --- /dev/null +++ b/site/public/assets/javascripts/mx/extensions/mx.movementsMobile.js @@ -0,0 +1,237 @@ + +MX.MobileMovements = function (cam) { + + var touching = true, + moving = false, + startTime = null, + v = 12, + vr = Math.PI * 0.012, + vx = vy = vz = 0; + + var directionLocked = false, + directionLockThreshold = 5 + + var pos = { x: 0, y: viewHeight, z: 0, rotationX: 0, rotationY: 0 } + + var pointX, pointY, deltaX, deltaY, distX = 0, distY = 0, absDistX = 0, absDistY = 0, startTime + + var rotationX = 0, rotationY = 0, destRotationX = 0, destRotationY = 0 + var rotationSum = 0 + var rotationMedian = 0 + var orientationMax = 0 + var samples = 0 + var sampleThreshold = 120 + var lastAlpha + + var is_portrait + + var exports = { + + init: function () { + exports.orientationchange() + + document.addEventListener("touchstart", exports.touchstart) + document.addEventListener("touchmove", exports.touchmove) + document.addEventListener("touchend", exports.touchend) + window.addEventListener('orientationchange', exports.orientationchange) + window.addEventListener("devicemotion", exports.devicemotion) + window.addEventListener("deviceorientation", exports.deviceorientation) + }, + touchstart: function(e){ + if (e.touches.length == 1) { + touching = true + + startTime = Date.now() + + var point = event.touches[0] + pointX = point.pageX + pointY = point.pageY + distX = distY = 0 + pos.x = cam.x + pos.z = cam.z + pos.rotationY = cam.rotationY + } + }, + touchmove: function(e){ + e.preventDefault() + if (e.touches.length == 1) { + + var timestamp = Date.now() + var point = event.touches[0] + deltaX = point.pageX - pointX + deltaY = point.pageY - pointY + + pointX = point.pageX + pointY = point.pageY + + distX += deltaX + distY += deltaY + absDistX = abs(distX) + absDistY = abs(distY) + } + }, + touchend: function(e){ + e.preventDefault() + if (e.touches.length == 0) { + touching = directionLocked = false + var timestamp = Date.now() + var duration = startTime - timestamp + if (duration < 300) { + } + } + }, + orientationchange: function(e){ + is_portrait = window.innerWidth < window.innerHeight + if (is_portrait) { + lastAlpha = 0 + } + }, + devicemotion: function(e) { + if (! is_portrait) return; + var rotationBeta = e.rotationRate.alpha; // weird! + rotationSum += rotationBeta; + samples += 1; + }, + deviceorientation: function (e) { + if (! lastAlpha) { lastAlpha = e.alpha } + is_portrait ? exports.portraitorientation(e) : exports.landscapeorientation(e) + }, + portraitorientation: function(e) { + // compass gives most accurate orientation in portrait mode + var alpha, dx = 0, dy = 0 + + if (e.webkitCompassHeading) { + alpha = 180 - e.webkitCompassHeading; + } + else { + alpha = 180 - e.alpha; + } + + // android rotates in reverse + if (is_android) { + alpha = 360 - alpha + } + + // use rotationRate to gauge if we've tilted the screen past vertical + // for looking at ceiling + if (e.beta > orientationMax) { + orientationMax = e.beta + rotationMedian = rotationSum + } + + // this number was only going to 83 max.. not 90.. weird + var beta = e.beta + 7; + + // if we've got enough motion data, we should be able to determine + // if we've tilted backwards. otherwise, lock to the horizon. + if (! is_android && samples > sampleThreshold) { + dx = rotationSum > rotationMedian ? e.beta - 90 : 90 - e.beta + } + else { + dx = 0 + } + + // avoid jumping around in a circle + if (Math.abs(alpha - lastAlpha) < 100 || Math.abs(alpha - lastAlpha) > 300) { + dy = alpha - lastAlpha + lastAlpha = alpha + } + + // avoid jumping around in a circle #2 + if (dy > 300) { + dy -= 360 + } else if (dy < -300) { + dy += 360 + } + + destRotationX = MX.toRad(dx) + destRotationY += MX.toRad(dy) + }, + + landscapeorientation: function (e) { + var dx, dy + + dx = e.gamma > 0 ? 90 - e.gamma : 90 + e.gamma + dy = e.alpha - lastAlpha + lastAlpha = e.alpha + + // avoid the sudden jump from 0 to -360 + if (dy > 300) { + dy -= 360 + } + else if (dy < -300) { + dy += 360 + } + + destRotationX = dx > 45 ? 0 : MX.toRad(dx) + destRotationY += MX.toRad(dy) + }, + + update: function () { + var drx, dry + + dry = (destRotationY - rotationY) / 6 + drx = (destRotationX - rotationX) / 6 + rotationY += dry + rotationX += drx + cam.rotationY = pos.rotationY += dry + cam.rotationX = pos.rotationX += drx + + if (distX || distY) { + var oldDistY = absDistY, oldDistX = absDistX + absDistY = avg(absDistY, 0, 5) + var dy = (oldDistY - absDistY) * sign(distY) * 2 + + absDistX = avg(absDistX, 0, 5) + var dx = (oldDistX - absDistX) * sign(distX) * 2 + + distY = sign(distY) * absDistY + distX = sign(distX) * absDistX + + pos.x -= dy * Math.cos(pos.rotationY + Math.PI / 2) + pos.z -= dy * Math.sin(pos.rotationY + Math.PI / 2) + cam.rotationY = pos.rotationY += dx / (window.innerWidth) * Math.PI / 2 + + app.tube("move", pos) + } + }, + + lock: function(){ locked = true }, + unlock: function(){ locked = false }, + scale: function(n){ if (n) scale = n; return scale }, + resetScale: function(n){ scale = DEFAULT_SCALE }, + gravity: function(g){ return typeof g == "boolean" ? gravity = g : gravity }, + velocity: function(n){ v = clamp(n, 1, 50) }, + jumpVelocity: function(n){ jumpV = clamp(n, 1, 50) }, + } + + return exports +} + + +// function momentum (current, start, time, lowerMargin, wrapperSize, deceleration) { +// var distance = current - start, +// speed = Math.abs(distance) / time, +// destination, +// duration; +// +// deceleration = deceleration === undefined ? 0.0006 : deceleration; +// +// destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 ); +// duration = speed / deceleration; +// +// if ( destination < lowerMargin ) { +// destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin; +// distance = Math.abs(destination - current); +// duration = distance / speed; +// } else if ( destination > 0 ) { +// destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0; +// distance = Math.abs(current) + destination; +// duration = distance / speed; +// } +// +// return { +// destination: Math.round(destination), +// duration: duration +// }; +// } diff --git a/site/public/assets/javascripts/mx/extensions/mx.orbitCamera.js b/site/public/assets/javascripts/mx/extensions/mx.orbitCamera.js new file mode 100644 index 0000000..6603ff4 --- /dev/null +++ b/site/public/assets/javascripts/mx/extensions/mx.orbitCamera.js @@ -0,0 +1,102 @@ +MX.OrbitCamera = function(opt){ + var exports = {}, bound = false + exports.opt = opt = defaults(opt, { + el: window, // object to bind events on + camera: scene.camera, // camera object we'll be moving + radius: 100, + radiusRange: [ 10, 1000 ], + rotationX: PI/2, + rotationY: 0, + center: { x: 0, y: 0, z: 0 }, + sensitivity: 10, // moving 1 pixel is like moving N radians + wheelSensitivity: 10, + ease: 10, + }) + var rx, ry, radius, px, py, epsilon = 1e-10, dragging = false + exports.init = function(){ + ry = opt.rotationY + rx = opt.rotationX + radius = opt.radius + exports.wheel = new wheel({ + el: opt.el, + update: function(e, delta){ + opt.radius = clamp( opt.radius + delta * opt.wheelSensitivity, opt.radiusRange[0], opt.radiusRange[1] ) + }, + }) + exports.bind() + } + exports.toggle = function(state){ + if (state) exports.bind() + else exports.unbind() + } + exports.bind = function(){ + if (bound) return; + bound = true + opt.el.addEventListener("mousedown", down) + window.addEventListener("mousemove", move) + window.addEventListener("mouseup", up) + exports.wheel.unlock() + } + exports.unbind = function(){ + if (! bound) return; + bound = false + opt.el.removeEventListener("mousedown", down) + window.removeEventListener("mousemove", move) + window.removeEventListener("mouseup", up) + exports.wheel.lock() + } + function down (e) { + px = e.pageX + py = e.pageY + dragging = true + } + function move (e) { + if (! dragging) return + exports.delta(px- e.pageX, py - e.pageY) + px = e.pageX + py = e.pageY + } + function up (e) { + dragging = false + } + exports.delta = function(x,y){ + opt.rotationY += x/window.innerWidth * opt.sensitivity + opt.rotationX = clamp( opt.rotationX + y/window.innerHeight * opt.sensitivity, 0, PI) + } + exports.zoom = function(r){ + opt.radius = r + } + exports.zoomDelta = function(r){ + opt.radius += r + } + exports.move = function(y, x){ + opt.rotationY = y + if (typeof x == "number") { opt.rotationX = x } + } + exports.update = function(){ + if (abs(ry - opt.rotationY) > epsilon) { + ry = avg(ry, opt.rotationY, opt.ease) + } + else { + ry = opt.rotationY + } + if (abs(rx - opt.rotationX) > epsilon) { + rx = avg(rx, opt.rotationX, opt.ease) + } + else { + rx = opt.rotationX + } + if (abs(radius - opt.radius) > epsilon) { + radius = avg(radius, opt.radius, opt.ease) + } + else { + radius = opt.radius + } + opt.camera.x = opt.center.x + radius * sin(rx) * cos(ry) + opt.camera.z = opt.center.y + radius * sin(rx) * sin(ry) + opt.camera.y = opt.center.z + radius * cos(rx) + opt.camera.rotationX = PI/2 - rx + opt.camera.rotationY = ry + PI/2 + } + return exports +} diff --git a/site/public/assets/javascripts/mx/extensions/mx.rotationControl.js b/site/public/assets/javascripts/mx/extensions/mx.rotationControl.js new file mode 100644 index 0000000..9adb627 --- /dev/null +++ b/site/public/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/site/public/assets/javascripts/mx/extensions/mx.scene.js b/site/public/assets/javascripts/mx/extensions/mx.scene.js new file mode 100644 index 0000000..8f11fb0 --- /dev/null +++ b/site/public/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/site/public/assets/javascripts/mx/extensions/mx.scrubber.js b/site/public/assets/javascripts/mx/extensions/mx.scrubber.js new file mode 100644 index 0000000..54612f2 --- /dev/null +++ b/site/public/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/site/public/assets/javascripts/mx/extensions/mx.unclampedOrbitCamera.js b/site/public/assets/javascripts/mx/extensions/mx.unclampedOrbitCamera.js new file mode 100644 index 0000000..28b1aac --- /dev/null +++ b/site/public/assets/javascripts/mx/extensions/mx.unclampedOrbitCamera.js @@ -0,0 +1,130 @@ +MX.OrbitCamera = function(opt){ + var exports = {}, bound = false + exports.opt = opt = defaults(opt, { + el: window, // object to bind events on + camera: scene.camera, // camera object we'll be moving + radius: 100, + radiusRange: [ 10, 1000 ], + rotationX: PI/2, + rotationY: 0, + center: { x: 0, y: 0, z: 0 }, + sensitivity: 10, // moving 1 pixel is like moving N radians + wheelSensitivity: 10, + ease: 10, + wheelEase: 10, + }) + var rx, ry, radius, px, py, epsilon = 1e-10 + exports.dragging = false + exports.init = function(){ + ry = opt.rotationY + rx = opt.rotationX + radius = opt.radius + exports.wheel = new wheel({ + el: opt.el, + update: function(e, delta){ + opt.radius = clamp( opt.radius + delta * opt.wheelSensitivity, opt.radiusRange[0], opt.radiusRange[1] ) + }, + }) + exports.bind() + } + exports.toggle = function(state){ + if (state) exports.bind() + else exports.unbind() + } + exports.bind = function(){ + if (bound) return; + bound = true + opt.el.addEventListener("mousedown", down) + window.addEventListener("mousemove", move) + window.addEventListener("mouseup", up) + opt.el.addEventListener("touchstart", touch(down)) + window.addEventListener("touchmove", touch(move)) + window.addEventListener("touchend", touch(up)) + exports.wheel.unlock() + } + exports.unbind = function(){ + if (! bound) return; + bound = false + opt.el.removeEventListener("mousedown", down) + window.removeEventListener("mousemove", move) + window.removeEventListener("mouseup", up) + exports.wheel.lock() + } + function cancelable (fn) { + return function(e){ + e.preventDefault() + fn(e) + } + } + function touch (fn){ + return function(e){ + fn(e.touches[0]) + } + } + function down (e) { + px = e.pageX + py = e.pageY + exports.dragging = true + } + function move (e) { + if (! exports.dragging) return + exports.delta(px- e.pageX, py - e.pageY) + px = e.pageX + py = e.pageY + } + function up (e) { + exports.dragging = false + } + exports.delta = function(x,y){ + opt.rotationY += x/window.innerWidth * opt.sensitivity + opt.rotationX = opt.rotationX + y/window.innerHeight * opt.sensitivity + } + exports.move = function(y, x){ + opt.rotationY = y + if (typeof x == "number") { opt.rotationX = x } + } + exports.zoom = function(r){ + opt.radius = r + } + exports.setZoom = function(r){ + radius = opt.radius = r + } + exports.zoomPercent = function(n){ + opt.radius = lerp(n, opt.radiusRange[0], opt.radiusRange[1]) + } + exports.zoomDelta = function(r){ + opt.radius += r + } + exports.pause = function(){ + var sy = sign(opt.rotationY-ry) + var sx = sign(opt.rotationX-rx) + opt.rotationY = ry + sy * 0.1 + opt.rotationX = rx + sx * 0.1 + } + exports.update = function(){ + if (abs(ry - opt.rotationY) > epsilon) { + ry = avg(ry, opt.rotationY, opt.ease) + } + else { + ry = opt.rotationY + } + if (abs(rx - opt.rotationX) > epsilon) { + rx = avg(rx, opt.rotationX, opt.ease) + } + else { + rx = opt.rotationX + } + if (abs(radius - opt.radius) > epsilon) { + radius = avg(radius, opt.radius, opt.wheelEase) + } + else { + radius = opt.radius + } + opt.camera.x = opt.center.x + radius * sin(rx) * cos(ry) + opt.camera.z = opt.center.y + radius * sin(rx) * sin(ry) + opt.camera.y = opt.center.z + radius * cos(rx) + opt.camera.rotationX = PI/2 - rx + opt.camera.rotationY = ry + PI/2 + } + return exports +} diff --git a/site/public/assets/javascripts/mx/mx.js b/site/public/assets/javascripts/mx/mx.js new file mode 100644 index 0000000..1ce7cda --- /dev/null +++ b/site/public/assets/javascripts/mx/mx.js @@ -0,0 +1,593 @@ +/** + * 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) * app_devicePixelRatio + || 0 + }, + set: function (val) { + width = val + this.el.style.width = (width/app_devicePixelRatio) + 'px' + } + }) + + Object.defineProperty(this, 'height', { + get: function () { + return height + || parseInt(self.el.style.height, 10) * app_devicePixelRatio + || 0 + }, + set: function (val) { + height = val + this.el.style.height = (height/app_devicePixelRatio) + '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(' + + (app_devicePixelRatio * this.scaleX).toFixed(floatPrecision) + ',' + + (app_devicePixelRatio * this.scaleY).toFixed(floatPrecision) + ',' + + (app_devicePixelRatio * 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) + parent.el.removeChild(child.el) + 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 MX.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 + }, + + move: function(ops){ + var layer = this + layer.ops = defaults(ops, layer.ops) + for (var i in ops) { + layer[i] = ops[i] + } + layer.dirty = true + layer.update() + }, + + 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 || 'undef' // _.uniqueId() + 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/site/public/assets/javascripts/mx/mx.min.js b/site/public/assets/javascripts/mx/mx.min.js new file mode 100644 index 0000000..b0f0cdd --- /dev/null +++ b/site/public/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/site/public/assets/javascripts/mx/mx.quaternion.js b/site/public/assets/javascripts/mx/mx.quaternion.js new file mode 100644 index 0000000..783f887 --- /dev/null +++ b/site/public/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/site/public/assets/javascripts/mx/mx.skew.js b/site/public/assets/javascripts/mx/mx.skew.js new file mode 100644 index 0000000..73296bd --- /dev/null +++ b/site/public/assets/javascripts/mx/mx.skew.js @@ -0,0 +1,610 @@ +/** + * 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) * app_devicePixelRatio + || 0 + }, + set: function (val) { + width = val + this.el.style.width = (width/app_devicePixelRatio) + 'px' + } + }) + + Object.defineProperty(this, 'height', { + get: function () { + return height + || parseInt(self.el.style.height, 10) * app_devicePixelRatio + || 0 + }, + set: function (val) { + height = val + this.el.style.height = (height/app_devicePixelRatio) + '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.skewX = this.__skewX = 0 + this.skewY = this.__skewY = 0 + 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.skewX !== this.__skewX || + this.skewY !== this.__skewY) { + this.__skewX = this.skewX + this.__skewY = this.skewY + 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(' + + (app_devicePixelRatio * this.scaleX).toFixed(floatPrecision) + ',' + + (app_devicePixelRatio * this.scaleY).toFixed(floatPrecision) + ',' + + (app_devicePixelRatio * this.scaleZ).toFixed(floatPrecision) + ') ' + + 'skew(' + + (app_devicePixelRatio * this.skewX).toFixed(floatPrecision) + 'rad,' + + (app_devicePixelRatio * this.skewY).toFixed(floatPrecision) + 'rad) ' + + 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) + parent.el.removeChild(child.el) + 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 MX.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 + }, + + setSkew: function (tar) { + this.skewX = (tar.x || tar.x === 0) ? tar.x : this.skewX + this.skewY = (tar.y || tar.y === 0) ? tar.y : this.skewY + }, + + 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 + }, + + move: function(ops){ + var layer = this + layer.ops = defaults(ops, layer.ops) + for (var i in ops) { + layer[i] = ops[i] + } + layer.dirty = true + layer.update() + }, + + 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 || 'undef' // _.uniqueId() + 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/site/public/assets/javascripts/mx/primitives/mx.image.js b/site/public/assets/javascripts/mx/primitives/mx.image.js new file mode 100644 index 0000000..39bb0b5 --- /dev/null +++ b/site/public/assets/javascripts/mx/primitives/mx.image.js @@ -0,0 +1,50 @@ +MX.Image = MX.Object3D.extend({ + init: function (ops) { + + this.type = "Image" + this.media = ops.media + this.width = 0 + this.height = 0 + this.x = ops.x || 0 + this.y = ops.y || 0 + this.z = ops.z || 0 + this.scale = ops.scale || 1 + this.backface = ops.backface || false + + ops.className && this.el.classList.add(ops.className) + this.backface && this.el.classList.add("backface-visible") + this.el.classList.add("image") + this.el.classList.add("mx-scenery") + + this.el.style.backgroundRepeat = 'no-repeat' + + this.load(ops) + }, + + load: function(ops){ + var layer = this + layer.ops = defaults(ops, layer.ops) + + var image = new Image() + image.onload = function(){ + if (! layer.ops) return + layer.scale = layer.ops.scale || 1 + layer.width = layer.ops.width || image.naturalWidth + layer.height = layer.ops.height || image.naturalHeight +// layer.x = layer.ops.x || 0 +// layer.y = layer.ops.y || 0 +// layer.z = layer.ops.z || 0 +// layer.rotationX = layer.ops.rotationX || 0 +// layer.rotationY = layer.ops.rotationY || 0 +// layer.rotationZ = layer.ops.rotationZ || 0 + layer.el.style.backgroundImage = "url(" + image.src + ")" + layer.el.classList.add('image') + layer.dirty = true + layer.ops.onload && layer.ops.onload( image ) + layer.update() + } + image.src = ops.src; + if (image.complete) setTimeout(image.onload) + }, + +}) diff --git a/site/public/assets/javascripts/mx/primitives/mx.soundcloud.js b/site/public/assets/javascripts/mx/primitives/mx.soundcloud.js new file mode 100644 index 0000000..75286d9 --- /dev/null +++ b/site/public/assets/javascripts/mx/primitives/mx.soundcloud.js @@ -0,0 +1,125 @@ +MX.Soundcloud = MX.Object3D.extend({ + init: function (ops) { + + this.type = "Soundcloud" + this.media = ops.media + this.width = 0 + this.height = 0 + this.x = ops.x || 0 + this.y = ops.y || 0 + this.z = ops.z || 0 + this.scale = ops.scale || 1 + this.backface = ops.backface || false + + ops.className && this.el.classList.add(ops.className) + this.backface && this.el.classList.add("backface-visible") + this.el.classList.add("audio") + this.el.classList.add("mx-scenery") + + this.el.style.backgroundRepeat = 'no-repeat' + this.paused = true + + this.ops = ops + }, + + load: function(ops){ + if (ops) { + ops = this.ops = defaults(ops, this.ops) + } + else { + ops = this.ops + } + + this.width = ops.media.width + this.height = ops.media.height + + var tag = Parser.lookup.soundcloud.tag(ops.media) + var $iframe = $(tag) + var iframe = $iframe[0] + $iframe.css('z-index', '-1') + this.el.appendChild( iframe ) + + var overlay = this.overlay = document.createElement("div") + overlay.style.width = "100%" + overlay.style.height = "100%" + overlay.style.position = "absolute" + overlay.style.top = "0" + overlay.style.left = "0" + overlay.style.zIndex = "2" + overlay.className = "overlay" + this.el.appendChild(overlay) + + this.player = SC.Widget( iframe ) + this.player.setVolume(80) + + this.duration = 0 + + this.player.bind(SC.Widget.Events.READY, this.ready.bind(this)) +// this.player.bind(SC.Widget.Events.LOAD_PROGRESS, this.loadProgress.bind(this)) +// this.player.bind(SC.Widget.Events.PLAY_PROGRESS, this.playProgress.bind(this)) + this.player.bind(SC.Widget.Events.PLAY, this.didPlay.bind(this)) + this.player.bind(SC.Widget.Events.PAUSE, this.didPause.bind(this)) + this.player.bind(SC.Widget.Events.FINISH, this.finished.bind(this)) + }, + + ready: function(){ + this.seek( this.media.keyframe || 0 ) + + if (this.media.autoplay) { + this.play() + } + + this.player.getDuration(function(duration){ + this.duration = duration + }.bind(this)) + }, + + play: function(){ + this.player.play() + }, + + pause: function(){ + this.player.pause() + }, + + toggle: function(state){ + if (typeof state === "boolean") { + if (state) this.play() + else this.pause() + } + else { + this.player.toggle() + } + }, + + seek: function(n){ + if (n < 1) { + n = n * this.duration + } + this.player.seekTo(n) + }, + + setLoop: function(state){ + this.media.loop = state + }, + + didPlay: function(){ + this.paused = false + }, + + didPause: function(){ + this.paused = true + }, + + finished: function(){ + console.log("soundcloud finished") + if (this.media.loop) { + this.seek(0) + this.play() + } + else if (this.bound) { + $(".playButton").removeClass('playing') + } + }, + +}) diff --git a/site/public/assets/javascripts/mx/primitives/mx.text.js b/site/public/assets/javascripts/mx/primitives/mx.text.js new file mode 100644 index 0000000..6b5681b --- /dev/null +++ b/site/public/assets/javascripts/mx/primitives/mx.text.js @@ -0,0 +1,59 @@ +MX.Text = MX.Object3D.extend({ + + init: function (ops) { + + this.type = "Text" + + this.type = "Image" + this.media = ops.media + this.width = 0 + this.height = 0 + this.x = ops.x || 0 + this.y = ops.y || 0 + this.z = ops.z || 0 + this.scale = ops.scale || 1 + this.backface = ops.backface || false + + this.scale = ops.scale || 1 + this.width = ops.media.width + this.height = ops.media.height + this.x = ops.x || 0 + this.y = ops.y || 0 + this.z = ops.z || 0 + this.rotationX = ops.rotationX || 0 + this.rotationY = ops.rotationY || 0 + this.rotationZ = ops.rotationZ || 0 + + ops.className && this.el.classList.add(ops.className) + this.backface && this.el.classList.add("backface-visible") + this.el.classList.add("mx-text") + this.el.classList.add("mx-scenery") + + this.inner = document.createElement("div") + this.inner.classList.add("inner") + this.el.appendChild(this.inner) + + this.load(ops) + }, + + load: function(ops){ + var media = ops.media + if (media.font) this.setFont(media.font) + + this.setText( media.description ) + }, + + setFont: function(font){ + if (! font.color || font.color[0] == "#") { font.color = [0,0,0] } + + this.inner.style.fontFamily = "'" + font.family + "',sans-serif" + this.el.style.fontSize = (2 * font.size / devicePixelRatio) + "pt" + this.el.style.textAlign = font.align + this.el.style.color = rgb_string(font.color) + }, + + setText: function(text){ + this.inner.innerHTML = marked( text || "" ) + }, + +}) diff --git a/site/public/assets/javascripts/mx/primitives/mx.video.js b/site/public/assets/javascripts/mx/primitives/mx.video.js new file mode 100644 index 0000000..c281f02 --- /dev/null +++ b/site/public/assets/javascripts/mx/primitives/mx.video.js @@ -0,0 +1,110 @@ +MX.Video = MX.Object3D.extend({ + + init: function (ops) { + + this.type = "Video" + + this.media = ops.media + this.width = ops.media.width + this.height = ops.media.height + this.x = ops.x || 0 + this.y = ops.y || 0 + this.z = ops.z || 0 + this.rotationX = ops.rotationX || 0 + this.rotationY = ops.rotationY || 0 + this.rotationZ = ops.rotationZ || 0 + this.scale = ops.scale || 1 + this.backface = ops.backface || false + + ops.className && this.el.classList.add(ops.className) + this.backface && this.el.classList.add("backface-visible") + this.el.classList.add("video") + this.el.classList.add("mx-scenery") + this.paused = !! this.media.autoplay + this.muted = app.muted || !! this.media.mute + }, + + load: function(ops){ + this.paused = true + + this.player = document.createElement('video') + this.player.addEventListener("loadedmetadata", this.ready.bind(this)) + this.player.addEventListener("error", this.error.bind(this)) + this.player.addEventListener("ended", this.finished.bind(this)) + this.player.width = "100%" + this.player.height = "100%" + this.player.src = this.media.url + this.player.load() + + this.el.appendChild(this.player) + }, + + ready: function(){ + this.seek( this.media.keyframe || 0 ) + + if (this.media.mute) { + this.mute() + } + else { + this.unmute() + } + + if (this.media.autoplay) { + this.play() + } + }, + + error: function(err){ + console.log("video error", err) + }, + + play: function(){ + this.paused = false + this.player.play() + }, + + pause: function(){ + this.paused = true + this.player.pause() + }, + + seek: function(n){ + if (n < 1) { + n = n * this.duration() + } + this.player.currentTime = n + }, + + mute: function(){ + this.player.muted = true + this.player.volume = 0 + this.muted = true + }, + + unmute: function(){ + this.player.muted = false + this.player.volume = 0.8 + this.muted = false + }, + + setLoop: function(state){ + this.media.loop = state + }, + + duration: function(){ + return this.player.duration + }, + + finished: function(){ + console.log("video finished") + if (this.media.loop) { + this.seek(0) + this.play() + } + else if (this.bound) { + $(".playButton").removeClass('playing') + } + }, + + +}) diff --git a/site/public/assets/javascripts/mx/primitives/mx.vimeo.js b/site/public/assets/javascripts/mx/primitives/mx.vimeo.js new file mode 100644 index 0000000..fe5ce86 --- /dev/null +++ b/site/public/assets/javascripts/mx/primitives/mx.vimeo.js @@ -0,0 +1,162 @@ +MX.Vimeo = MX.Object3D.extend({ + + init: function (ops) { + + this.type = "Vimeo" + + this.media = ops.media + this.width = ops.media.width + this.height = ops.media.height + this.x = ops.x || 0 + this.y = ops.y || 0 + this.z = ops.z || 0 + this.rotationX = ops.rotationX || 0 + this.rotationY = ops.rotationY || 0 + this.rotationZ = ops.rotationZ || 0 + this.scale = ops.scale || 1 + this.backface = ops.backface || false + + ops.className && this.el.classList.add(ops.className) + this.backface && this.el.classList.add("backface-visible") + this.el.classList.add("video") + this.el.classList.add("mx-scenery") + this.paused = !! this.media.autoplay + this.muted = app.muted || !! this.media.mute + this.started = false + }, + + load: function (ops) { + var uid = 'player-' + Uid () + var loop = this.media.loop ? 'loop=1' : "" + var preload = document.createElement("iframe") + preload.id = uid + preload.setAttribute("src", "//player.vimeo.com/video/" + this.media.token + "?api=1&badge=0&controls=0branding=0&byline=0&portrait=0&title=0&" + loop + "&player_id=" + uid) + preload.style.backgroundImage = "url(" + this.media.thumbnail + ")" + preload.style.width = "100%" + preload.style.height = "100%" + preload.style.border = "0" + preload.style.pointerEvents = "none" + preload.className = "preload" + this.el.appendChild(preload) + this.player = $f(preload) + + this.player.addEvent('ready', $.proxy(this.ready, this)) + }, + + ready: function(){ + console.log("vimeo ready") + + this.started = true + + // wait until ready before binding events. other events: play, pause + this.player.addEvent('play', $.proxy(this.onPlay, this)) + this.player.addEvent('pause', $.proxy(this.onPause, this)) + this.player.addEvent('finish', $.proxy(this.finished, this)) + + // this is async on vimeo so call it asap + this.player.api('getDuration', $.proxy(function(n){ + console.log("vimeo duration", n) + this.player.duration = n + }, this)) + + if (this.media.mute) { + this.mute() + } + else { + this.unmute() + } + + this.seek( this.media.keyframe || 0 ) + + if (this.media.autoplay) { + this.play() + } + else { + this.pause() + } + }, + + error: function(err){ + console.log("vimeo error", err) + }, + + play: function(){ + this.paused = false + this.player.api('play') + }, + + pause: function(){ + this.paused = true + this.player.api('pause') + }, + + seek: function(n){ + // defer seek until we have duration + if (! this.duration()) { + setTimeout($.proxy(function(){ + this.seek(n) + }, this), 300) + return + } + + if (! this.started || n === 0) { + return + } + + if (n < 1) { + n = n * this.duration() + } + this.player.api('seekTo', max(0, n-1)) + if (this.paused) { + this.paused = false + this.play() + this.pause() + setTimeout($.proxy(function(){ + this.pause() + }, this), 100) + } + }, + + duration: function(){ + return this.player.duration + }, + + mute: function(){ + this.player.api('setVolume', 0.0) + this.muted = true + }, + + unmute: function(){ + this.player.api('setVolume', 0.8) + this.muted = false + }, + + setLoop: function(state){ + this.media.loop = state + this.player.api('setLoop', state) + }, + + onPlay: function(){ + if (this.paused) { + this.pause() + } + }, + + onPause: function(){ + if (! this.paused) { + this.play() + } + }, + + finished: function(){ +// if (this.media.loop) { +// this.seek(0) +// this.play() +// } +// else if (this.bound) { + if (! this.media.loop && this.bound) { + $(".playButton").removeClass('playing') + } + } + +}) diff --git a/site/public/assets/javascripts/mx/primitives/mx.youtube.js b/site/public/assets/javascripts/mx/primitives/mx.youtube.js new file mode 100644 index 0000000..5c92378 --- /dev/null +++ b/site/public/assets/javascripts/mx/primitives/mx.youtube.js @@ -0,0 +1,196 @@ +MX.Youtube = MX.Object3D.extend({ + + init: function (ops) { + + this.type = "Youtube" + + this.media = ops.media + this.width = ops.media.width + this.height = ops.media.height + this.x = ops.x || 0 + this.y = ops.y || 0 + this.z = ops.z || 0 + this.rotationX = ops.rotationX || 0 + this.rotationY = ops.rotationY || 0 + this.rotationZ = ops.rotationZ || 0 + this.scale = ops.scale || 1 + this.backface = ops.backface || false + + ops.className && this.el.classList.add(ops.className) + this.backface && this.el.classList.add("backface-visible") + this.el.classList.add("video") + this.el.classList.add("mx-scenery") + this.paused = !! this.media.autoplay + this.muted = app.muted || !! this.media.mute + }, + + load: function (ops) { + var base = this + var uid = 'player-' + Uid () + var preload = document.createElement("div") + preload.id = uid + preload.style.backgroundImage = "url(" + this.media.thumbnail + ")" + preload.style.backgroundSize = "cover" + preload.style.width = "100%" + preload.style.height = "100%" + preload.style.pointerEvents = "none" + preload.style.position = "absolute" + preload.style.top = "0" + preload.style.left = "0" + preload.style.zIndex = "1" + preload.className = "preload" + this.el.appendChild(preload) + + var overlay = this.overlay = document.createElement("div") + overlay.style.width = "100%" + overlay.style.height = "100%" + overlay.style.position = "absolute" + overlay.style.top = "0" + overlay.style.left = "0" + overlay.style.zIndex = "2" + overlay.className = "overlay" + this.el.appendChild(overlay) + this.defer(uid) + }, + + defer: function (uid){ + if (! YT || ! YT.loaded) { + setTimeout(function(){ + this.defer(uid) + }.bind(this), 300) + } + else { + // not sure why i need to delay here.. + // stopped working until i added the setTimeout + setTimeout(function(){ + this.build(uid) + }.bind(this), 20) + } + }, + + build: function(uid){ + this.player = new YT.Player(uid, { + videoId: this.media.token, + width: this.width, + height: this.height, + events: { + onReady: this.ready.bind(this), + onError: this.error.bind(this), + onStateChange: this.statechange.bind(this), + }, + playerVars: { + autohide: 1, + autoplay: 0, + disablekb: 1, + controls: 0, + enablejsapi: 1, + origin: window.location.origin, + fs: 0, + modestbranding: 1, + iv_load_policy: 3, // no annotations + loop: 0, + showinfo: 0, + rel: 0, + wmode: 'opaque', + }, + }) + }, + + ready: function(){ + console.log("youtube ready") + + if (this.media.autoplay) { + this.play() + } + else { + this.pause() + } + + if (this.media.mute) { + this.mute() + } + else { + this.unmute() + } + + this.seek( this.media.keyframe || 0 ) + }, + + error: function(err){ + console.log("youtube error", err) + }, + + statechange: function(e){ + switch (e.data) { + case -1: // unstarted + break + case 0: // finished + this.finished() + break + case 1: // play + if (this.paused) { + this.pause() + } + break + case 2: // pause + break + case 3: // buffering + break + case 5: // cued + break + } + }, + + play: function(){ + this.paused = false + this.player.playVideo() + }, + + pause: function(){ + this.paused = true + this.player.pauseVideo() + }, + + seek: function(n, allowSeekAhead){ + if (n < 1) { + n = n * this.duration() + } + allowSeekAhead = typeof allowSeekAhead == "boolean" ? allowSeekAhead : true + this.player.seekTo(n, true) // set to false if seeking manually + }, + + duration: function(){ + return this.player.getDuration() + }, + + mute: function(){ + this.player.mute() + this.muted = true + }, + + unmute: function(){ + this.player.unMute() + this.player.setVolume(80) + this.muted = false + }, + + setLoop: function(state){ + this.media.loop = state + }, + + finished: function(){ + console.log("youtube finished") + if (this.media.loop) { + this.seek(0) + this.play() + } + else if (this.bound) { + $(".playButton").removeClass('playing') + } + } + +}) + +window.onYouTubePlayerAPIReady = function(){ + // console.log("youtube api ready") +} |
