UNPKG

@hughsk/fulltilt

Version:

Standalone device orientation + device motion normalization and conversion library

1,383 lines (865 loc) 28.5 kB
/** * * FULL TILT * http://github.com/richtr/Full-Tilt * * A standalone DeviceOrientation + DeviceMotion JavaScript library that * normalises orientation sensor input, applies relevant screen orientation * transforms, returns Euler Angle, Quaternion and Rotation * Matrix representations back to web developers and provides conversion * between all supported orientation representation types. * * Copyright: 2014 Rich Tibbett * License: MIT * */ (function ( window ) { // Only initialize the FULLTILT API if it is not already attached to the DOM if ( window.FULLTILT !== undefined && window.FULLTILT !== null ) { return; } var M_PI = Math.PI; var M_PI_2 = M_PI / 2; var M_2_PI = 2 * M_PI; // Degree to Radian conversion var degToRad = M_PI / 180; var radToDeg = 180 / M_PI; // Internal device orientation + motion variables var sensors = { "orientation": { active: false, callbacks: [], data: undefined }, "motion": { active: false, callbacks: [], data: undefined } }; var screenActive = false; // Internal screen orientation variables var hasScreenOrientationAPI = window.screen && window.screen.orientation && window.screen.orientation.angle !== undefined && window.screen.orientation.angle !== null ? true : false; var screenOrientationAngle = ( hasScreenOrientationAPI ? window.screen.orientation.angle : ( window.orientation || 0 ) ) * degToRad; var SCREEN_ROTATION_0 = 0, SCREEN_ROTATION_90 = M_PI_2, SCREEN_ROTATION_180 = M_PI, SCREEN_ROTATION_270 = M_2_PI / 3, SCREEN_ROTATION_MINUS_90 = - M_PI_2; // Math.sign polyfill function sign(x) { x = +x; // convert to a number if (x === 0 || isNaN(x)) return x; return x > 0 ? 1 : -1; } ///// Promise-based Sensor Data checker ////// function SensorCheck(sensorRootObj) { var promise = new Promise(function(resolve, reject) { var runCheck = function (tries) { setTimeout(function() { if (sensorRootObj && sensorRootObj.data) { resolve(); } else if (tries >= 20) { reject(); } else { runCheck(++tries); } }, 50); }; runCheck(0); }); return promise; } ////// Internal Event Handlers ////// function handleScreenOrientationChange () { if ( hasScreenOrientationAPI ) { screenOrientationAngle = ( window.screen.orientation.angle || 0 ) * degToRad; } else { screenOrientationAngle = ( window.orientation || 0 ) * degToRad; } } function handleDeviceOrientationChange ( event ) { sensors.orientation.data = event; // Fire every callback function each time deviceorientation is updated for ( var i in sensors.orientation.callbacks ) { sensors.orientation.callbacks[ i ].call( this ); } } function handleDeviceMotionChange ( event ) { sensors.motion.data = event; // Fire every callback function each time devicemotion is updated for ( var i in sensors.motion.callbacks ) { sensors.motion.callbacks[ i ].call( this ); } } ///// FULLTILT API Root Object ///// var FULLTILT = {}; FULLTILT.version = "0.5.3"; ///// FULLTILT API Root Methods ///// FULLTILT.getDeviceOrientation = function(options) { var promise = new Promise(function(resolve, reject) { var control = new FULLTILT.DeviceOrientation(options); control.start(); var orientationSensorCheck = new SensorCheck(sensors.orientation); orientationSensorCheck.then(function() { resolve(control); }).catch(function() { control.stop(); reject('DeviceOrientation is not supported'); }); }); return promise; }; FULLTILT.getDeviceMotion = function(options) { var promise = new Promise(function(resolve, reject) { var control = new FULLTILT.DeviceMotion(options); control.start(); var motionSensorCheck = new SensorCheck(sensors.motion); motionSensorCheck.then(function() { resolve(control); }).catch(function() { control.stop(); reject('DeviceMotion is not supported'); }); }); return promise; }; ////// FULLTILT.Quaternion ////// FULLTILT.Quaternion = function ( x, y, z, w ) { var quat, outQuat; this.set = function ( x, y, z, w ) { this.x = x || 0; this.y = y || 0; this.z = z || 0; this.w = w || 1; }; this.copy = function ( quaternion ) { this.x = quaternion.x; this.y = quaternion.y; this.z = quaternion.z; this.w = quaternion.w; }; this.setFromEuler = (function () { var _x, _y, _z; var _x_2, _y_2, _z_2; var cX, cY, cZ, sX, sY, sZ; return function ( euler ) { euler = euler || {}; _z = ( euler.alpha || 0 ) * degToRad; _x = ( euler.beta || 0 ) * degToRad; _y = ( euler.gamma || 0 ) * degToRad; _z_2 = _z / 2; _x_2 = _x / 2; _y_2 = _y / 2; cX = Math.cos( _x_2 ); cY = Math.cos( _y_2 ); cZ = Math.cos( _z_2 ); sX = Math.sin( _x_2 ); sY = Math.sin( _y_2 ); sZ = Math.sin( _z_2 ); this.set( sX * cY * cZ - cX * sY * sZ, // x cX * sY * cZ + sX * cY * sZ, // y cX * cY * sZ + sX * sY * cZ, // z cX * cY * cZ - sX * sY * sZ // w ); this.normalize(); return this; }; })(); this.setFromRotationMatrix = (function () { var R; return function( matrix ) { R = matrix.elements; this.set( 0.5 * Math.sqrt( 1 + R[0] - R[4] - R[8] ) * sign( R[7] - R[5] ), // x 0.5 * Math.sqrt( 1 - R[0] + R[4] - R[8] ) * sign( R[2] - R[6] ), // y 0.5 * Math.sqrt( 1 - R[0] - R[4] + R[8] ) * sign( R[3] - R[1] ), // z 0.5 * Math.sqrt( 1 + R[0] + R[4] + R[8] ) // w ); return this; }; })(); this.multiply = function ( quaternion ) { outQuat = FULLTILT.Quaternion.prototype.multiplyQuaternions( this, quaternion ); this.copy( outQuat ); return this; }; this.rotateX = function ( angle ) { outQuat = FULLTILT.Quaternion.prototype.rotateByAxisAngle( this, [ 1, 0, 0 ], angle ); this.copy( outQuat ); return this; }; this.rotateY = function ( angle ) { outQuat = FULLTILT.Quaternion.prototype.rotateByAxisAngle( this, [ 0, 1, 0 ], angle ); this.copy( outQuat ); return this; }; this.rotateZ = function ( angle ) { outQuat = FULLTILT.Quaternion.prototype.rotateByAxisAngle( this, [ 0, 0, 1 ], angle ); this.copy( outQuat ); return this; }; this.normalize = function () { return FULLTILT.Quaternion.prototype.normalize( this ); }; // Initialize object values this.set( x, y, z, w ); }; FULLTILT.Quaternion.prototype = { constructor: FULLTILT.Quaternion, multiplyQuaternions: function () { var multipliedQuat = new FULLTILT.Quaternion(); return function ( a, b ) { 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; multipliedQuat.set( qax * qbw + qaw * qbx + qay * qbz - qaz * qby, // x qay * qbw + qaw * qby + qaz * qbx - qax * qbz, // y qaz * qbw + qaw * qbz + qax * qby - qay * qbx, // z qaw * qbw - qax * qbx - qay * qby - qaz * qbz // w ); return multipliedQuat; }; }(), normalize: function( q ) { var len = Math.sqrt( q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w ); if ( len === 0 ) { q.x = 0; q.y = 0; q.z = 0; q.w = 1; } else { len = 1 / len; q.x *= len; q.y *= len; q.z *= len; q.w *= len; } return q; }, rotateByAxisAngle: function () { var outputQuaternion = new FULLTILT.Quaternion(); var transformQuaternion = new FULLTILT.Quaternion(); var halfAngle, sA; return function ( targetQuaternion, axis, angle ) { halfAngle = ( angle || 0 ) / 2; sA = Math.sin( halfAngle ); transformQuaternion.set( ( axis[ 0 ] || 0 ) * sA, // x ( axis[ 1 ] || 0 ) * sA, // y ( axis[ 2 ] || 0 ) * sA, // z Math.cos( halfAngle ) // w ); // Multiply quaternion by q outputQuaternion = FULLTILT.Quaternion.prototype.multiplyQuaternions( targetQuaternion, transformQuaternion ); return FULLTILT.Quaternion.prototype.normalize( outputQuaternion ); }; }() }; ////// FULLTILT.RotationMatrix ////// FULLTILT.RotationMatrix = function ( m11, m12, m13, m21, m22, m23, m31, m32, m33 ) { var outMatrix; this.elements = new Float32Array( 9 ); this.identity = function () { this.set( 1, 0, 0, 0, 1, 0, 0, 0, 1 ); return this; }; this.set = function ( m11, m12, m13, m21, m22, m23, m31, m32, m33 ) { this.elements[ 0 ] = m11 || 1; this.elements[ 1 ] = m12 || 0; this.elements[ 2 ] = m13 || 0; this.elements[ 3 ] = m21 || 0; this.elements[ 4 ] = m22 || 1; this.elements[ 5 ] = m23 || 0; this.elements[ 6 ] = m31 || 0; this.elements[ 7 ] = m32 || 0; this.elements[ 8 ] = m33 || 1; }; this.copy = function ( matrix ) { this.elements[ 0 ] = matrix.elements[ 0 ]; this.elements[ 1 ] = matrix.elements[ 1 ]; this.elements[ 2 ] = matrix.elements[ 2 ]; this.elements[ 3 ] = matrix.elements[ 3 ]; this.elements[ 4 ] = matrix.elements[ 4 ]; this.elements[ 5 ] = matrix.elements[ 5 ]; this.elements[ 6 ] = matrix.elements[ 6 ]; this.elements[ 7 ] = matrix.elements[ 7 ]; this.elements[ 8 ] = matrix.elements[ 8 ]; }; this.setFromEuler = (function() { var _x, _y, _z; var cX, cY, cZ, sX, sY, sZ; return function ( euler ) { euler = euler || {}; _z = ( euler.alpha || 0 ) * degToRad; _x = ( euler.beta || 0 ) * degToRad; _y = ( euler.gamma || 0 ) * degToRad; cX = Math.cos( _x ); cY = Math.cos( _y ); cZ = Math.cos( _z ); sX = Math.sin( _x ); sY = Math.sin( _y ); sZ = Math.sin( _z ); // // ZXY-ordered rotation matrix construction. // this.set( cZ * cY - sZ * sX * sY, // 1,1 - cX * sZ, // 1,2 cY * sZ * sX + cZ * sY, // 1,3 cY * sZ + cZ * sX * sY, // 2,1 cZ * cX, // 2,2 sZ * sY - cZ * cY * sX, // 2,3 - cX * sY, // 3,1 sX, // 3,2 cX * cY // 3,3 ); this.normalize(); return this; }; })(); this.setFromQuaternion = (function() { var sqw, sqx, sqy, sqz; return function( q ) { sqw = q.w * q.w; sqx = q.x * q.x; sqy = q.y * q.y; sqz = q.z * q.z; this.set( sqw + sqx - sqy - sqz, // 1,1 2 * (q.x * q.y - q.w * q.z), // 1,2 2 * (q.x * q.z + q.w * q.y), // 1,3 2 * (q.x * q.y + q.w * q.z), // 2,1 sqw - sqx + sqy - sqz, // 2,2 2 * (q.y * q.z - q.w * q.x), // 2,3 2 * (q.x * q.z - q.w * q.y), // 3,1 2 * (q.y * q.z + q.w * q.x), // 3,2 sqw - sqx - sqy + sqz // 3,3 ); return this; }; })(); this.multiply = function ( m ) { outMatrix = FULLTILT.RotationMatrix.prototype.multiplyMatrices( this, m ); this.copy( outMatrix ); return this; }; this.rotateX = function ( angle ) { outMatrix = FULLTILT.RotationMatrix.prototype.rotateByAxisAngle( this, [ 1, 0, 0 ], angle ); this.copy( outMatrix ); return this; }; this.rotateY = function ( angle ) { outMatrix = FULLTILT.RotationMatrix.prototype.rotateByAxisAngle( this, [ 0, 1, 0 ], angle ); this.copy( outMatrix ); return this; }; this.rotateZ = function ( angle ) { outMatrix = FULLTILT.RotationMatrix.prototype.rotateByAxisAngle( this, [ 0, 0, 1 ], angle ); this.copy( outMatrix ); return this; }; this.normalize = function () { return FULLTILT.RotationMatrix.prototype.normalize( this ); }; // Initialize object values this.set( m11, m12, m13, m21, m22, m23, m31, m32, m33 ); }; FULLTILT.RotationMatrix.prototype = { constructor: FULLTILT.RotationMatrix, multiplyMatrices: function () { var matrix = new FULLTILT.RotationMatrix(); var aE, bE; return function ( a, b ) { aE = a.elements; bE = b.elements; matrix.set( aE[0] * bE[0] + aE[1] * bE[3] + aE[2] * bE[6], aE[0] * bE[1] + aE[1] * bE[4] + aE[2] * bE[7], aE[0] * bE[2] + aE[1] * bE[5] + aE[2] * bE[8], aE[3] * bE[0] + aE[4] * bE[3] + aE[5] * bE[6], aE[3] * bE[1] + aE[4] * bE[4] + aE[5] * bE[7], aE[3] * bE[2] + aE[4] * bE[5] + aE[5] * bE[8], aE[6] * bE[0] + aE[7] * bE[3] + aE[8] * bE[6], aE[6] * bE[1] + aE[7] * bE[4] + aE[8] * bE[7], aE[6] * bE[2] + aE[7] * bE[5] + aE[8] * bE[8] ); return matrix; }; }(), normalize: function( matrix ) { var R = matrix.elements; // Calculate matrix determinant var determinant = R[0] * R[4] * R[8] - R[0] * R[5] * R[7] - R[1] * R[3] * R[8] + R[1] * R[5] * R[6] + R[2] * R[3] * R[7] - R[2] * R[4] * R[6]; // Normalize matrix values R[0] /= determinant; R[1] /= determinant; R[2] /= determinant; R[3] /= determinant; R[4] /= determinant; R[5] /= determinant; R[6] /= determinant; R[7] /= determinant; R[8] /= determinant; matrix.elements = R; return matrix; }, rotateByAxisAngle: function () { var outputMatrix = new FULLTILT.RotationMatrix(); var transformMatrix = new FULLTILT.RotationMatrix(); var sA, cA; var validAxis = false; return function ( targetRotationMatrix, axis, angle ) { transformMatrix.identity(); // reset transform matrix validAxis = false; sA = Math.sin( angle ); cA = Math.cos( angle ); if ( axis[ 0 ] === 1 && axis[ 1 ] === 0 && axis[ 2 ] === 0 ) { // x validAxis = true; transformMatrix.elements[4] = cA; transformMatrix.elements[5] = -sA; transformMatrix.elements[7] = sA; transformMatrix.elements[8] = cA; } else if ( axis[ 1 ] === 1 && axis[ 0 ] === 0 && axis[ 2 ] === 0 ) { // y validAxis = true; transformMatrix.elements[0] = cA; transformMatrix.elements[2] = sA; transformMatrix.elements[6] = -sA; transformMatrix.elements[8] = cA; } else if ( axis[ 2 ] === 1 && axis[ 0 ] === 0 && axis[ 1 ] === 0 ) { // z validAxis = true; transformMatrix.elements[0] = cA; transformMatrix.elements[1] = -sA; transformMatrix.elements[3] = sA; transformMatrix.elements[4] = cA; } if ( validAxis ) { outputMatrix = FULLTILT.RotationMatrix.prototype.multiplyMatrices( targetRotationMatrix, transformMatrix ); outputMatrix = FULLTILT.RotationMatrix.prototype.normalize( outputMatrix ); } else { outputMatrix = targetRotationMatrix; } return outputMatrix; }; }() }; ////// FULLTILT.Euler ////// FULLTILT.Euler = function ( alpha, beta, gamma ) { this.set = function ( alpha, beta, gamma ) { this.alpha = alpha || 0; this.beta = beta || 0; this.gamma = gamma || 0; }; this.copy = function ( inEuler ) { this.alpha = inEuler.alpha; this.beta = inEuler.beta; this.gamma = inEuler.gamma; }; this.setFromRotationMatrix = (function () { var R, _alpha, _beta, _gamma; return function ( matrix ) { R = matrix.elements; if (R[8] > 0) { // cos(beta) > 0 _alpha = Math.atan2(-R[1], R[4]); _beta = Math.asin(R[7]); // beta (-pi/2, pi/2) _gamma = Math.atan2(-R[6], R[8]); // gamma (-pi/2, pi/2) } else if (R[8] < 0) { // cos(beta) < 0 _alpha = Math.atan2(R[1], -R[4]); _beta = -Math.asin(R[7]); _beta += (_beta >= 0) ? - M_PI : M_PI; // beta [-pi,-pi/2) U (pi/2,pi) _gamma = Math.atan2(R[6], -R[8]); // gamma (-pi/2, pi/2) } else { // R[8] == 0 if (R[6] > 0) { // cos(gamma) == 0, cos(beta) > 0 _alpha = Math.atan2(-R[1], R[4]); _beta = Math.asin(R[7]); // beta [-pi/2, pi/2] _gamma = - M_PI_2; // gamma = -pi/2 } else if (R[6] < 0) { // cos(gamma) == 0, cos(beta) < 0 _alpha = Math.atan2(R[1], -R[4]); _beta = -Math.asin(R[7]); _beta += (_beta >= 0) ? - M_PI : M_PI; // beta [-pi,-pi/2) U (pi/2,pi) _gamma = - M_PI_2; // gamma = -pi/2 } else { // R[6] == 0, cos(beta) == 0 // gimbal lock discontinuity _alpha = Math.atan2(R[3], R[0]); _beta = (R[7] > 0) ? M_PI_2 : - M_PI_2; // beta = +-pi/2 _gamma = 0; // gamma = 0 } } // alpha is in [-pi, pi], make sure it is in [0, 2*pi). if (_alpha < 0) { _alpha += M_2_PI; // alpha [0, 2*pi) } // Convert to degrees _alpha *= radToDeg; _beta *= radToDeg; _gamma *= radToDeg; // apply derived euler angles to current object this.set( _alpha, _beta, _gamma ); }; })(); this.setFromQuaternion = (function () { var _alpha, _beta, _gamma; return function ( q ) { var sqw = q.w * q.w; var sqx = q.x * q.x; var sqy = q.y * q.y; var sqz = q.z * q.z; var unitLength = sqw + sqx + sqy + sqz; // Normalised == 1, otherwise correction divisor. var wxyz = q.w * q.x + q.y * q.z; var epsilon = 1e-6; // rounding factor if (wxyz > (0.5 - epsilon) * unitLength) { _alpha = 2 * Math.atan2(q.y, q.w); _beta = M_PI_2; _gamma = 0; } else if (wxyz < (-0.5 + epsilon) * unitLength) { _alpha = -2 * Math.atan2(q.y, q.w); _beta = -M_PI_2; _gamma = 0; } else { var aX = sqw - sqx + sqy - sqz; var aY = 2 * (q.w * q.z - q.x * q.y); var gX = sqw - sqx - sqy + sqz; var gY = 2 * (q.w * q.y - q.x * q.z); if (gX > 0) { _alpha = Math.atan2(aY, aX); _beta = Math.asin(2 * wxyz / unitLength); _gamma = Math.atan2(gY, gX); } else { _alpha = Math.atan2(-aY, -aX); _beta = -Math.asin(2 * wxyz / unitLength); _beta += _beta < 0 ? M_PI : - M_PI; _gamma = Math.atan2(-gY, -gX); } } // alpha is in [-pi, pi], make sure it is in [0, 2*pi). if (_alpha < 0) { _alpha += M_2_PI; // alpha [0, 2*pi) } // Convert to degrees _alpha *= radToDeg; _beta *= radToDeg; _gamma *= radToDeg; // apply derived euler angles to current object this.set( _alpha, _beta, _gamma ); }; })(); this.rotateX = function ( angle ) { FULLTILT.Euler.prototype.rotateByAxisAngle( this, [ 1, 0, 0 ], angle ); return this; }; this.rotateY = function ( angle ) { FULLTILT.Euler.prototype.rotateByAxisAngle( this, [ 0, 1, 0 ], angle ); return this; }; this.rotateZ = function ( angle ) { FULLTILT.Euler.prototype.rotateByAxisAngle( this, [ 0, 0, 1 ], angle ); return this; }; // Initialize object values this.set( alpha, beta, gamma ); }; FULLTILT.Euler.prototype = { constructor: FULLTILT.Euler, rotateByAxisAngle: function () { var _matrix = new FULLTILT.RotationMatrix(); var outEuler; return function ( targetEuler, axis, angle ) { _matrix.setFromEuler( targetEuler ); _matrix = FULLTILT.RotationMatrix.prototype.rotateByAxisAngle( _matrix, axis, angle ); targetEuler.setFromRotationMatrix( _matrix ); return targetEuler; }; }() }; ///// FULLTILT.DeviceOrientation ////// FULLTILT.DeviceOrientation = function (options) { this.options = options || {}; // by default use UA deviceorientation 'type' ("game" on iOS, "world" on Android) var tries = 0; var maxTries = 200; var successCount = 0; var successThreshold = 10; this.alphaOffsetScreen = 0; this.alphaOffsetDevice = undefined; // Create a game-based deviceorientation object (initial alpha === 0 degrees) if (this.options.type === "game") { var setGameAlphaOffset = function(evt) { if (evt.alpha !== null) { // do regardless of whether 'evt.absolute' is also true this.alphaOffsetDevice = new FULLTILT.Euler(evt.alpha, 0, 0); this.alphaOffsetDevice.rotateZ( -screenOrientationAngle ); // Discard first {successThreshold} responses while a better compass lock is found by UA if(++successCount >= successThreshold) { window.removeEventListener( 'deviceorientation', setGameAlphaOffset, false ); return; } } if(++tries >= maxTries) { window.removeEventListener( 'deviceorientation', setGameAlphaOffset, false ); } }.bind(this); window.addEventListener( 'deviceorientation', setGameAlphaOffset, false ); // Create a compass-based deviceorientation object (initial alpha === compass degrees) } else if (this.options.type === "world") { var setCompassAlphaOffset = function(evt) { if (evt.absolute !== true && evt.webkitCompassAccuracy !== undefined && evt.webkitCompassAccuracy !== null && +evt.webkitCompassAccuracy >= 0 && +evt.webkitCompassAccuracy < 50) { this.alphaOffsetDevice = new FULLTILT.Euler(evt.webkitCompassHeading, 0, 0); this.alphaOffsetDevice.rotateZ( screenOrientationAngle ); this.alphaOffsetScreen = screenOrientationAngle; // Discard first {successThreshold} responses while a better compass lock is found by UA if(++successCount >= successThreshold) { window.removeEventListener( 'deviceorientation', setCompassAlphaOffset, false ); return; } } if(++tries >= maxTries) { window.removeEventListener( 'deviceorientation', setCompassAlphaOffset, false ); } }.bind(this); window.addEventListener( 'deviceorientation', setCompassAlphaOffset, false ); } // else... use whatever orientation system the UA provides ("game" on iOS, "world" on Android) }; FULLTILT.DeviceOrientation.prototype = { constructor: FULLTILT.DeviceOrientation, start: function ( callback ) { if ( callback && Object.prototype.toString.call( callback ) == '[object Function]' ) { sensors.orientation.callbacks.push( callback ); } if( !screenActive ) { if ( hasScreenOrientationAPI ) { window.screen.orientation.addEventListener( 'change', handleScreenOrientationChange, false ); } else { window.addEventListener( 'orientationchange', handleScreenOrientationChange, false ); } } if ( !sensors.orientation.active ) { window.addEventListener( 'deviceorientation', handleDeviceOrientationChange, false ); sensors.orientation.active = true; } }, stop: function () { if ( sensors.orientation.active ) { window.removeEventListener( 'deviceorientation', handleDeviceOrientationChange, false ); sensors.orientation.active = false; } }, listen: function( callback ) { this.start( callback ); }, getFixedFrameQuaternion: (function () { var euler = new FULLTILT.Euler(); var matrix = new FULLTILT.RotationMatrix(); var quaternion = new FULLTILT.Quaternion(); return function() { var orientationData = sensors.orientation.data || { alpha: 0, beta: 0, gamma: 0 }; var adjustedAlpha = orientationData.alpha; if (this.alphaOffsetDevice) { matrix.setFromEuler( this.alphaOffsetDevice ); matrix.rotateZ( - this.alphaOffsetScreen ); euler.setFromRotationMatrix( matrix ); if (euler.alpha < 0) { euler.alpha += 360; } euler.alpha %= 360; adjustedAlpha -= euler.alpha; } euler.set( adjustedAlpha, orientationData.beta, orientationData.gamma ); quaternion.setFromEuler( euler ); return quaternion; }; })(), getScreenAdjustedQuaternion: (function () { var quaternion; return function() { quaternion = this.getFixedFrameQuaternion(); // Automatically apply screen orientation transform quaternion.rotateZ( - screenOrientationAngle ); return quaternion; }; })(), getFixedFrameMatrix: (function () { var euler = new FULLTILT.Euler(); var matrix = new FULLTILT.RotationMatrix(); return function () { var orientationData = sensors.orientation.data || { alpha: 0, beta: 0, gamma: 0 }; var adjustedAlpha = orientationData.alpha; if (this.alphaOffsetDevice) { matrix.setFromEuler( this.alphaOffsetDevice ); matrix.rotateZ( - this.alphaOffsetScreen ); euler.setFromRotationMatrix( matrix ); if (euler.alpha < 0) { euler.alpha += 360; } euler.alpha %= 360; adjustedAlpha -= euler.alpha; } euler.set( adjustedAlpha, orientationData.beta, orientationData.gamma ); matrix.setFromEuler( euler ); return matrix; }; })(), getScreenAdjustedMatrix: (function () { var matrix; return function () { matrix = this.getFixedFrameMatrix(); // Automatically apply screen orientation transform matrix.rotateZ( - screenOrientationAngle ); return matrix; }; })(), getFixedFrameEuler: (function () { var euler = new FULLTILT.Euler(); var matrix; return function () { matrix = this.getFixedFrameMatrix(); euler.setFromRotationMatrix( matrix ); return euler; }; })(), getScreenAdjustedEuler: (function () { var euler = new FULLTILT.Euler(); var matrix; return function () { matrix = this.getScreenAdjustedMatrix(); euler.setFromRotationMatrix( matrix ); return euler; }; })(), isAbsolute: function () { if ( sensors.orientation.data && sensors.orientation.data.absolute === true ) { return true; } return false; }, getLastRawEventData: function () { return sensors.orientation.data || {}; }, ALPHA: 'alpha', BETA: 'beta', GAMMA: 'gamma' }; ///// FULLTILT.DeviceMotion ////// FULLTILT.DeviceMotion = function (options) { this.options = options || {}; // placeholder object since no options are currently supported }; FULLTILT.DeviceMotion.prototype = { constructor: FULLTILT.DeviceMotion, start: function ( callback ) { if ( callback && Object.prototype.toString.call( callback ) == '[object Function]' ) { sensors.motion.callbacks.push( callback ); } if( !screenActive ) { if ( hasScreenOrientationAPI ) { window.screen.orientation.addEventListener( 'change', handleScreenOrientationChange, false ); } else { window.addEventListener( 'orientationchange', handleScreenOrientationChange, false ); } } if ( !sensors.motion.active ) { window.addEventListener( 'devicemotion', handleDeviceMotionChange, false ); sensors.motion.active = true; } }, stop: function () { if ( sensors.motion.active ) { window.removeEventListener( 'devicemotion', handleDeviceMotionChange, false ); sensors.motion.active = false; } }, listen: function( callback ) { this.start( callback ); }, getScreenAdjustedAcceleration: function () { var accData = sensors.motion.data && sensors.motion.data.acceleration ? sensors.motion.data.acceleration : { x: 0, y: 0, z: 0 }; var screenAccData = {}; switch ( screenOrientationAngle ) { case SCREEN_ROTATION_90: screenAccData.x = - accData.y; screenAccData.y = accData.x; break; case SCREEN_ROTATION_180: screenAccData.x = - accData.x; screenAccData.y = - accData.y; break; case SCREEN_ROTATION_270: case SCREEN_ROTATION_MINUS_90: screenAccData.x = accData.y; screenAccData.y = - accData.x; break; default: // SCREEN_ROTATION_0 screenAccData.x = accData.x; screenAccData.y = accData.y; break; } screenAccData.z = accData.z; return screenAccData; }, getScreenAdjustedAccelerationIncludingGravity: function () { var accGData = sensors.motion.data && sensors.motion.data.accelerationIncludingGravity ? sensors.motion.data.accelerationIncludingGravity : { x: 0, y: 0, z: 0 }; var screenAccGData = {}; switch ( screenOrientationAngle ) { case SCREEN_ROTATION_90: screenAccGData.x = - accGData.y; screenAccGData.y = accGData.x; break; case SCREEN_ROTATION_180: screenAccGData.x = - accGData.x; screenAccGData.y = - accGData.y; break; case SCREEN_ROTATION_270: case SCREEN_ROTATION_MINUS_90: screenAccGData.x = accGData.y; screenAccGData.y = - accGData.x; break; default: // SCREEN_ROTATION_0 screenAccGData.x = accGData.x; screenAccGData.y = accGData.y; break; } screenAccGData.z = accGData.z; return screenAccGData; }, getScreenAdjustedRotationRate: function () { var rotRateData = sensors.motion.data && sensors.motion.data.rotationRate ? sensors.motion.data.rotationRate : { alpha: 0, beta: 0, gamma: 0 }; var screenRotRateData = {}; switch ( screenOrientationAngle ) { case SCREEN_ROTATION_90: screenRotRateData.beta = - rotRateData.gamma; screenRotRateData.gamma = rotRateData.beta; break; case SCREEN_ROTATION_180: screenRotRateData.beta = - rotRateData.beta; screenRotRateData.gamma = - rotRateData.gamma; break; case SCREEN_ROTATION_270: case SCREEN_ROTATION_MINUS_90: screenRotRateData.beta = rotRateData.gamma; screenRotRateData.gamma = - rotRateData.beta; break; default: // SCREEN_ROTATION_0 screenRotRateData.beta = rotRateData.beta; screenRotRateData.gamma = rotRateData.gamma; break; } screenRotRateData.alpha = rotRateData.alpha; return screenRotRateData; }, getLastRawEventData: function () { return sensors.motion.data || {}; } }; ////// Attach FULLTILT to root DOM element ////// window.FULLTILT = FULLTILT; })( window );