Repository

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;
    }
}