diff options
Diffstat (limited to 'client/assets/javascripts/mx/extensions')
4 files changed, 906 insertions, 0 deletions
diff --git a/client/assets/javascripts/mx/extensions/mx.movements.js b/client/assets/javascripts/mx/extensions/mx.movements.js new file mode 100644 index 0000000..074ca9a --- /dev/null +++ b/client/assets/javascripts/mx/extensions/mx.movements.js @@ -0,0 +1,285 @@ + + +MX.Movements = function (cam, viewHeight) { + + var moveForward, + moveLeft, + moveBackward, + moveRight, + moveUp, + moveDown, + turnLeft, + turnRight, + turnUp, + turnDown, + jumping = false, + creeping = false, + locked = false, + gravity = false, + rotationX_min = PI/-2, + rotationX_max = PI/2 + + var v = 12, + vr = Math.PI * 0.012, + jumpV = 23, + vx = vy = vz = 0, + creepFactor = 0.3 + + var DEFAULT_SCALE = scale = 1.0 + + var pos = { x: 0, y: 0, z: 0, rotationX: 0, rotationY: 0 } + + return { + + init: function () { + + document.addEventListener('keydown', function (e) { + // console.log(e.keyCode) + if (locked) return; + switch ( e.keyCode ) { + + case 16: // shift + creeping = true + break + + case 38: // up + case 87: // w + moveForward = true + break + + case 37: // left + case 65: // a + moveLeft = true + break + + case 40: // down + case 83: // s + moveBackward = true + break + + case 39: // right + case 68: // d + moveRight = true + break + + case 81: // q + turnLeft = true + break + + case 69: // e + turnRight = true + break + + case 82: // r + turnUp = true + break + + case 70: // f + turnDown = true + break + + case 32: // space + if (gravity) { + jumping = true + vy = abs(vy) + jumpV * scale + if (e.shiftKey) { + vy *= -1 + } + } + else { + if (e.shiftKey) { + moveDown = true + } + else { + moveUp = true + } + } + break + + case 27: // esc + map.toggle() + break + } + }) + + document.addEventListener('keyup', function (e) { + if (locked) return; + switch ( e.keyCode ) { + + case 16: // shift + creeping = false + break + + case 38: // up + case 87: // w + moveForward = false + break + + case 37: // left + case 65: // a + moveLeft = false + break + + case 40: // down + case 83: // s + moveBackward = false + break + + case 39: // right + case 68: // d + moveRight = false + break + + case 81: // q + turnLeft = false + break + + case 69: // e + turnRight = false + break + + case 82: // r + turnUp = false + break + + case 70: // f + turnDown = false + break + + case 32: // space + moveUp = moveDown = false + break + + case 48: // 0 + cam.rotationX = 0 + cam.rotationY = 0 + cam.x = 0 + cam.y = viewHeight + cam.z = 0 + break + } + }) + + var mouseX, mouseY, dx, dy, rotX, rotY, dragging = false + document.addEventListener('mousedown', function (e) { + if (locked) return; + mouseX = e.pageX + mouseY = e.pageY + rotX = cam.rotationX + rotY = cam.rotationY + dragging = true + }) + + document.addEventListener('mousemove', function (e) { + if (locked || ! dragging || app.dragging) return + var dx = (e.pageX - mouseX) / window.innerWidth * Math.PI/3 + var dy = (e.pageY - mouseY) / window.innerHeight * Math.PI/3 + cam.rotationY = rotY + dx + cam.rotationX = clamp( rotX - dy, rotationX_min, rotationX_max ) + }) + + document.addEventListener('mouseup', function (e) { + app.dragging = dragging = false + }) + + window.addEventListener('blur', reset) + window.addEventListener('focus', reset) + function reset(){ + moveForward = moveLeft = moveBackward = moveRight = moveUp = moveDown = turnLeft = turnRight = jumping = dragging = creeping = false + } + + }, + + update: function () { + + if (locked) return; + + var ry = cam.rotationY + var s = creeping ? scale * creepFactor : scale + var vrrrr = creeping ? vr * creepFactor * 5 : vr + var moving = moveForward || moveBackward || moveRight || moveLeft || moveUp || moveDown || turnLeft || turnRight || turnUp || turnDown + vx = vz = 0 + + pos.x = cam.x + pos.z = cam.z + + if (moving) { + + if (moveForward) { + vx += v * Math.cos(ry + Math.PI / 2) * s + vz += v * Math.sin(ry + Math.PI / 2) * s + } + if (moveBackward) { + vx -= v * Math.cos(ry + Math.PI / 2) * s + vz -= v * Math.sin(ry + Math.PI / 2) * s + } + if (moveLeft) { + vx -= v * Math.cos(ry) * s + vz -= v * Math.sin(ry) * s + } + if (moveRight) { + vx += v * Math.cos(ry) * s + vz += v * Math.sin(ry) * s + } + if (moveUp) { + pos.y += v * scale + } + if (moveDown) { + pos.y -= v * scale + } + + if (turnUp) { + cam.rotationX = clamp( cam.rotationX - vrrrr*s, rotationX_min, rotationX_max) + } + if (turnDown) { + cam.rotationX = clamp( cam.rotationX + vrrrr*s, rotationX_min, rotationX_max) + } + 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 + vz = vz || 1 + } + + var ceiling = (Rooms.mover.room ? Rooms.mover.room.height : 5000) + + if (pos.y >= ceiling) { + vy = 0 + pos.y = ceiling + vz = vz || 1 + } + } + + 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) }, + } +} diff --git a/client/assets/javascripts/mx/extensions/mx.rotationControl.js b/client/assets/javascripts/mx/extensions/mx.rotationControl.js new file mode 100644 index 0000000..9adb627 --- /dev/null +++ b/client/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/client/assets/javascripts/mx/extensions/mx.scene.js b/client/assets/javascripts/mx/extensions/mx.scene.js new file mode 100644 index 0000000..8f11fb0 --- /dev/null +++ b/client/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/client/assets/javascripts/mx/extensions/mx.scrubber.js b/client/assets/javascripts/mx/extensions/mx.scrubber.js new file mode 100644 index 0000000..54612f2 --- /dev/null +++ b/client/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; +} |
