@hughsk/fulltilt
Version:
Standalone device orientation + device motion normalization and conversion library
1,383 lines (865 loc) • 28.5 kB
JavaScript
/**
*
* 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 );