js/Mobilizing/input/Motion.js
import * as debug from '../core/util/Debug';
import Component from '../core/Component';
import Device from '../core/util/Device';
/**
* Fired when a new acceleration is available
* @event acceleration
*/
const EVT_ACC = 'acceleration';
/**
* Fired when a new acceleration including gravity is available
* @event accelerationIncludingGravity
*/
const EVT_ACC_GRAVITY = 'accelerationIncludingGravity';
/**
* Fired when a new gravity vector is available
* @event accelerationGravityVector
*/
const EVT_ACC_GRAVITY_VECTOR = 'accelerationGravityVector';
/**
* Fired when a new rotationrate data is available
* @event rotationrate
*/
const EVT_ROTATION_RATE = 'rotationrate';
export default class Motion extends Component {
/**
* Motion gives access to the embbeded accelerometers data of you device.
* !WARNING! On iOS 13+ you must call on() in a user interaction (i.e "touchend" on Mobile Safari). See examples!
*/
constructor() {
super(...arguments);
/**
* true if the acceloremeters are available on the device, false otherwise
* @type {Boolean}
*/
this.available = window.DeviceMotionEvent;
}
setup() {
if (!this._setupDone) {
/**
The accel rotation rate. This can also be get through the event rotationRate
@type {Object}
@property x
@property y
@property z
*/
this.rotationRate = { "x": 0, "y": 0, "z": 0 };
/**
The accel values including gravity. This can also be get through the event accelerationIncludingGravity
@type {Object}
@property x
@property y
@property z
*/
this.acc = { "x": 0, "y": 0, "z": 0 };
/**
The accel values without gravity. This can also be get through the event acceleration
@type {Object}
@property x
@property y
@property z
*/
this.userAcc = { "x": 0, "y": 0, "z": 0 };
/**
the gravity orientation vector based on accel including gravity. This can also be get through the event accelerationGravityVector
@type {Object}
@property x
@property y
@property z
*/
this.gravityVector = { "x": 0, "y": 0, "z": 0 };
this.smoothedAcc = { "x": 0, "y": 0, "z": 0 };
this.accel = [0, 0, 0];
this.gAccel = [0, 0, 0];
this.kFilteringFactor = 0.07;
super.setup();
}
}
on() {
if (this.available) {
super.on();
//manage iOS 13+ fucking security system : must activate sensors on "click"
if (Device.getOS() === "iOS") {
if (typeof window.DeviceMotionEvent.requestPermission === 'function') {
window.DeviceMotionEvent.requestPermission().then((permissionState) => {
if (permissionState === "granted") {
window.addEventListener("devicemotion", (event) => this.onDeviceMotion(event));
}
}).catch(console.error);
}
else {
// handle regular non iOS 13+ devices
window.addEventListener('devicemotion', (event) => this.onDeviceMotion(event));
}
}
else {
window.addEventListener('devicemotion', (event) => this.onDeviceMotion(event));
}
}
else {
debug.info("this device doesn't have acceloremeters");
}
}
off() {
super.off();
window.removeEventListener('devicemotion', (event) => this.onDeviceMotion(event));
}
onDeviceMotion(event) {
//Chrome manage everything reversed to Safari. We use Safari as 1, Chrome as -1
let reverse = 1;
if (Device.getOS() === "Android") {
reverse = -1;
}
if (event.acceleration) {
this.userAcc.x = event.acceleration.x * reverse;
this.userAcc.y = event.acceleration.y * reverse;
this.userAcc.z = event.acceleration.z * reverse;
//if the device doesn't support acceleration without gravity
if (event.acceleration.x === null || event.acceleration.x === undefined) {
this.computeUserAcc();
}
this.events.trigger(EVT_ACC, this.userAcc);
}
if (event.accelerationIncludingGravity) {
this.acc.x = event.accelerationIncludingGravity.x * reverse;
this.acc.y = event.accelerationIncludingGravity.y * reverse;
this.acc.z = event.accelerationIncludingGravity.z * reverse;
//propagate event to user script
this.events.trigger(EVT_ACC_GRAVITY, this.acc);
this.computeGravityVector();
//propagate custom event to user script
this.events.trigger(EVT_ACC_GRAVITY_VECTOR, this.gravityVector);
}
if (event.rotationRate) {
this.rotationRate.x = event.rotationRate.alpha;
this.rotationRate.y = event.rotationRate.beta;
this.rotationRate.z = event.rotationRate.gamma;
//propagate custom event to user script
this.events.trigger(EVT_ROTATION_RATE, this.rotationRate);
}
}
/**
* Compute the values for the acc on devices that gives "undefined" for the event.acceleration prop (some Android)
* @private
*/
computeUserAcc() {
this.accel[0] = this.acc.x * this.kFilteringFactor + this.accel[0] * (1.0 - this.kFilteringFactor);
this.accel[1] = this.acc.y * this.kFilteringFactor + this.accel[1] * (1.0 - this.kFilteringFactor);
this.accel[2] = this.acc.z * this.kFilteringFactor + this.accel[2] * (1.0 - this.kFilteringFactor);
this.userAcc.x = this.acc.x - this.accel[0];
this.userAcc.y = this.acc.y - this.accel[1];
this.userAcc.z = this.acc.z - this.accel[2];
}
/**
* Method to compute the gravity orientation vector based on accel including gravity.
* The result can also be get through the event onAccelerationGravityVector
*
* @private
*/
computeGravityVector() {
this.gAccel[0] = (this.acc.x / 10) * this.kFilteringFactor + this.gAccel[0] * (1.0 - this.kFilteringFactor);
this.gAccel[1] = (this.acc.y / 10) * this.kFilteringFactor + this.gAccel[1] * (1.0 - this.kFilteringFactor);
this.gAccel[2] = (this.acc.z / 10) * this.kFilteringFactor + this.gAccel[2] * (1.0 - this.kFilteringFactor);
this.gravityVector.z = this.gAccel[2] * (360 / (2 * Math.PI)) * 1.1;
this.gravityVector.x = this.gAccel[1] * (360 / (2 * Math.PI)) * 1.1;
this.gravityVector.y = Math.atan2(this.gAccel[0], this.gAccel[1]) * (180.0 / Math.PI);
}
/**
* Method to get the filtered accel including gravity.
*
* @param {Number} factor The number to use for the filtering aglorithm (0.07 gives good results)
* @return {Object} smoothedAcc object composed by x, y & z components
*/
getSmoothedAcc(factor) {
this.smoothedAcc.x = this.acc.x * factor + this.smoothedAcc.x * (1.0 - factor);
this.smoothedAcc.y = this.acc.y * factor + this.smoothedAcc.y * (1.0 - factor);
this.smoothedAcc.z = this.acc.z * factor + this.smoothedAcc.z * (1.0 - factor);
return this.smoothedAcc;
}
}