Repository

js/Mobilizing/input/Orientation.js

import Component from "../core/Component";
//import * as _Math from "../core/util/Math";
import Device from '../core/util/Device';

/**
* Fired when a new orientation is available
* @event deviceorientation
*/
const EVT_DEVICE_ORIENTATION = "deviceorientation";

/**
* Fired when calibration is required
* @event compassneedscalibration
*/
const EVT_COMPASS_CALIBRATION = "compassneedscalibration";

/**
* Uses built-in compass and/or gyroscope to produce orientation Data. Some heplers functions are in there to generate quarternion that can be applied on a camera or a 3D object's transform.
* !WARNING! On iOS 13+ you must call on() in a user interaction (i.e "click" or equivalent). See examples!
*/
export default class Orientation extends Component {

    constructor() {
        super(...arguments)

        /**
        * true if the magnetometer/compass is available on the device, false otherwise
        */
        this.available = window.DeviceOrientationEvent;
    }

    setup() {
        if (!this._setupDone) {
            /**
            The compass/gyro current raw values.
            This can also be get with the onDeviceOrientation event from the user script.
            @type {Object}
            @property alpha
            @property gamma
            @property beta
            @property heading
            */
            this.compass = {
                alpha: null,
                gamma: null,
                beta: null,
                heading: null
            };

            this._baseHeading = 0;
            this.rotationMatrix = [];
            super.setup();
        }

    }

    on() {
        super.on();

        //manage iOS 13+ fucking security system : must activate sensors on "click"
        if (Device.getOS() === "iOS") {
            if (typeof window.DeviceOrientationEvent.requestPermission === 'function') {
                window.DeviceOrientationEvent.requestPermission().then((permissionState) => {
                    if (permissionState === "granted") {
                        window.addEventListener("deviceorientation", (event) => this.onDeviceOrientation(event));
                    }
                }).catch(console.error);
            }
            else {
                // handle regular non iOS 13+ devices
                window.addEventListener("deviceorientation", (event) => this.onDeviceOrientation(event));
            }
        }
        else {
            window.addEventListener("deviceorientation", (event) => this.onDeviceOrientation(event));
        }
        window.addEventListener("compassneedscalibration", (event) => this.onCompassCalibration(event));
    }

    off() {
        super.off();

        window.removeEventListener("deviceorientation", (event) => this.onDeviceOrientation(event));
        window.removeEventListener("compassneedscalibration", (event) => this.onCompassCalibration(event));
    }

    /**
    * Reset the horizontal rotation to make a kind of realtime recalibration of the rotation
    */
    resetDirection() {
        if (this.compass.heading) {
            this._baseHeading = this.compass.heading;
        }
    }

    /**
    * compass calibration event callback
    * @private
    * @param {Object} event
    */
    onCompassCalibration(event) {
        //alert("onCompassCalibration call", event);
        this.events.trigger(EVT_COMPASS_CALIBRATION, event);
    }

    /**
    * callback used to access compass event.
    *
    * @private
    * @param {Object} event
    */
    onDeviceOrientation(event) {
        this.compass.alpha = event.alpha;//x
        this.compass.beta = event.beta;//y
        this.compass.gamma = event.gamma;//z

        //this.compass.heading = event.compassHeading || event.webkitCompassHeading || event.mozCompassHeading || event.ieCompassHeading;// || -event.alpha;

        //When the device doesn't generate a heading information, find a substitute.
        if (event.webkitCompassHeading) {
            this.compass.heading = event.webkitCompassHeading;
        }
        else {
            this.compass.heading = -event.alpha;
        }

        this.rotationMatrix = this.getRotationMatrix(this.compass.alpha, this.compass.beta, this.compass.gamma);

        this.events.trigger(EVT_DEVICE_ORIENTATION, this.compass);
    }

    /**
     * ©https://www.w3.org/TR/orientation-event/
     * @param {Number} alpha 
     * @param {Number} beta 
     * @param {Number} gamma 
     * @returns 
     */
    getRotationMatrix(alpha, beta, gamma) {

        const degtorad = Math.PI / 180; // Degree-to-Radian conversion
        const _x = beta ? beta * degtorad : 0; // beta value
        const _y = gamma ? gamma * degtorad : 0; // gamma value
        const _z = alpha ? alpha * degtorad : 0; // alpha value

        const cX = Math.cos(_x);
        const cY = Math.cos(_y);
        const cZ = Math.cos(_z);
        const sX = Math.sin(_x);
        const sY = Math.sin(_y);
        const sZ = Math.sin(_z);

        // ZXY rotation matrix construction.
        const m11 = cZ * cY - sZ * sX * sY;
        const m12 = - cX * sZ;
        const m13 = cY * sZ * sX + cZ * sY;

        const m21 = cY * sZ + cZ * sX * sY;
        const m22 = cZ * cX;
        const m23 = sZ * sY - cZ * cY * sX;

        const m31 = - cX * sY;
        const m32 = sX;
        const m33 = cX * cY;

        return [
            m11, m12, m13,
            m21, m22, m23,
            m31, m32, m33
        ];
    }
}