MX.OrbitCameraMobile = 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 var rotationSum = 0 var rotationMedian = 0 var orientationMax = 0 var samples = 0 var sampleThreshold = 20 var lastAlpha 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.orientationchange() } exports.toggle = function(state){ if (state) exports.bind() else exports.unbind() } exports.bind = function(){ if (bound) return; bound = true opt.el.addEventListener("touchstart", touch(down)) window.addEventListener("touchmove", touch(move)) window.addEventListener("touchend", touch(up)) window.addEventListener('orientationchange', exports.orientationchange) window.addEventListener("devicemotion", exports.devicemotion) window.addEventListener("deviceorientation", exports.deviceorientation) exports.wheel.unlock() } exports.unbind = function(){ if (! bound) return; bound = false 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.orientationchange = function(e){ is_portrait = window.innerWidth < window.innerHeight if (is_portrait) { lastAlpha = 0 } } exports.devicemotion = function(e) { if (! is_portrait) return; var rotationBeta = e.rotationRate.alpha; // weird! rotationSum += rotationBeta; samples += 1; } exports.deviceorientation = function (e) { if (! lastAlpha) { lastAlpha = e.alpha } is_portrait ? exports.portraitorientation(e) : exports.landscapeorientation(e) } exports.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 } opt.rotationX = MX.toRad(dx * -5) opt.rotationY += MX.toRad(dy * 10) } exports.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 } opt.rotationX = dx > 45 ? 0 : MX.toRad(dx) opt.rotationY += MX.toRad(dy) } 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 }