<NME> GamepadControl.js
<BEF> import Base from '../core/Base';
import Vector3 from '../math/Vector3';
import vendor from '../core/vendor';

/**
 * Gamepad Control plugin.
 *
 * @constructor clay.plugin.GamepadControl
 *
 * @example
 *   init: function(app) {
 *     this._gamepadControl = new clay.plugin.GamepadControl({
 *         target: camera,
 *         onStandardGamepadReady: customCallback
 *     });
 *   },
 *
 *   loop: function(app) {
 *     this._gamepadControl.update(app.frameTime);
 *   }
 */
var GamepadControl = Base.extend(function() {

    return /** @lends clay.plugin.GamepadControl# */ {

        /**
         * Scene node to control, mostly it is a camera.
         *
         * @type {clay.Node}
         */
        target: null,

        /**
         * Move speed.
         *
         * @type {number}
         */
        moveSpeed: 0.1,

        /**
         * Look around speed.
         *
         * @type {number}
         */
        lookAroundSpeed: 0.1,

        /**
         * Up axis.
         *
         * @type {clay.Vector3}
         */
        up: new Vector3(0, 1, 0),

        /**
         * Timeline.
         *
         * @type {clay.Timeline}
         */
        timeline: null,

        /**
         * Function to be called when a standard gamepad is ready to use.
         *
         * @type {function}
         */
        onStandardGamepadReady: function(gamepad){},

        /**
         * Function to be called when a gamepad is disconnected.
         *
         * @type {function}
         */
        onGamepadDisconnected: function(gamepad){},

        // Private properties:

        _moveForward: false,
        _moveBackward: false,
        _moveLeft: false,
        _moveRight: false,

        _offsetPitch: 0,
        _offsetRoll: 0,

        _connectedGamepadIndex: 0,
        _standardGamepadAvailable: false,
        _gamepadAxisThreshold: 0.3

    };

}, function() {

    this._checkGamepadCompatibility = this._checkGamepadCompatibility.bind(this);
    this._disconnectGamepad = this._disconnectGamepad.bind(this);
    this._getStandardGamepad = this._getStandardGamepad.bind(this);
    this._scanPressedGamepadButtons = this._scanPressedGamepadButtons.bind(this);
    this._scanInclinedGamepadAxes = this._scanInclinedGamepadAxes.bind(this);

    // If browser supports Gamepad API:
    if (typeof navigator.getGamepads === 'function') {
        this.init();
    }

},
/** @lends clay.plugin.GamepadControl.prototype */
{
    /**
     * Init. control.
     */
    init: function() {

        /**
         * When user begins to interact with connected gamepad:
         *
         * @see https://w3c.github.io/gamepad/#dom-gamepadevent
         */
        vendor.addEventListener(window, 'gamepadconnected', this._checkGamepadCompatibility);

        if (this.timeline) {
            this.timeline.on('frame', this.update);
        }

        vendor.addEventListener(window, 'gamepaddisconnected', this._disconnectGamepad);

    },

    /**
     * Dispose control.
     */
    dispose: function() {

        vendor.removeEventListener(window, 'gamepadconnected', this._checkGamepadCompatibility);

        if (this.timeline) {
            this.timeline.off('frame', this.update);
        }

        vendor.removeEventListener(window, 'gamepaddisconnected', this._disconnectGamepad);

    },

    /**
     * Control's update. Should be invoked every frame.
     *
     * @param {number} frameTime Frame time.
     */
    update: function (frameTime) {

        if (!this._standardGamepadAvailable) {
            return;
        }

        this._scanPressedGamepadButtons();
        this._scanInclinedGamepadAxes();

        // Update target depending on user input.

        var target = this.target;

        var position = this.target.position;
        var xAxis = target.localTransform.x.normalize();
        var zAxis = target.localTransform.z.normalize();

        var moveSpeed = this.moveSpeed * frameTime / 20;

        if (this._moveForward) {
            // Opposite direction of z.
            position.scaleAndAdd(zAxis, -moveSpeed);
        }
        if (this._moveBackward) {
            position.scaleAndAdd(zAxis, moveSpeed);
        }
        if (this._moveLeft) {
            position.scaleAndAdd(xAxis, -moveSpeed);
        }
        if (this._moveRight) {
            position.scaleAndAdd(xAxis, moveSpeed);
        }

        target.rotateAround(target.position, this.up, -this._offsetPitch * frameTime * Math.PI / 360);
        var xAxis = target.localTransform.x;
        target.rotateAround(target.position, xAxis, -this._offsetRoll * frameTime * Math.PI / 360);

        // Reset values to avoid lost of control.

        this._moveForward = this._moveBackward = this._moveLeft = this._moveRight = false;
        this._offsetPitch = this._offsetRoll = 0;

    },

    // Private methods:

    _checkGamepadCompatibility: function(event) {

        /**
         * If connected gamepad has a **standard** layout:
         *
         * @see https://w3c.github.io/gamepad/#remapping about standard.
         */
        if (event.gamepad.mapping === 'standard') {

            this._standardGamepadIndex = event.gamepad.index;
            this._standardGamepadAvailable = true;

            this.onStandardGamepadReady(event.gamepad);

        }

    },

    _disconnectGamepad: function(event) {

        this._standardGamepadAvailable = false;

        this.onGamepadDisconnected(event.gamepad);

    },

    _getStandardGamepad: function() {

        return navigator.getGamepads()[this._standardGamepadIndex];

    },

    _scanPressedGamepadButtons: function() {

        var gamepadButtons = this._getStandardGamepad().buttons;

        // For each gamepad button:
        for (var gamepadButtonId = 0; gamepadButtonId < gamepadButtons.length; gamepadButtonId++) {

            // Get user input.
            var gamepadButton = gamepadButtons[gamepadButtonId];

            if (gamepadButton.pressed) {

                switch (gamepadButtonId) {

                    // D-pad Up
                    case 12:
                        this._moveForward = true;
                        break;

                    // D-pad Down
                    case 13:
                        this._moveBackward = true;
                        break;

                    // D-pad Left
                    case 14:
                        this._moveLeft = true;
                        break;

                    // D-pad Right
                    case 15:
                        this._moveRight = true;
                        break;

                }

            }

        }

    },

    _scanInclinedGamepadAxes: function() {

        var gamepadAxes = this._getStandardGamepad().axes;

        // For each gamepad axis:
        for (var gamepadAxisId = 0; gamepadAxisId < gamepadAxes.length; gamepadAxisId++) {

            // Get user input.
            var gamepadAxis = gamepadAxes[gamepadAxisId];

            // XXX We use a threshold because axes are never neutral.
            if (Math.abs(gamepadAxis) > this._gamepadAxisThreshold) {

                switch (gamepadAxisId) {

                    // Left stick X±
                    case 0:
                        this._moveLeft = gamepadAxis < 0;
                        this._moveRight = gamepadAxis > 0;
                        break;

                    // Left stick Y±
                    case 1:
                        this._moveForward = gamepadAxis < 0;
                        this._moveBackward = gamepadAxis > 0;
                        break;

                    // Right stick X±
                    case 2:
                        this._offsetPitch += gamepadAxis * this.lookAroundSpeed;
                        break;

                    // Right stick Y±
                    case 3:
                        this._offsetRoll += gamepadAxis * this.lookAroundSpeed;
                        break;

                }

            }

        }

    }

});

export default GamepadControl;

<MSG> Fix GamepadControl timeline param.

<DFF> @@ -96,6 +96,8 @@ var GamepadControl = Base.extend(function() {
     this._scanPressedGamepadButtons = this._scanPressedGamepadButtons.bind(this);
     this._scanInclinedGamepadAxes = this._scanInclinedGamepadAxes.bind(this);
 
+    this.update = this.update.bind(this);
+
     // If browser supports Gamepad API:
     if (typeof navigator.getGamepads === 'function') {
         this.init();
@@ -181,6 +183,16 @@ var GamepadControl = Base.extend(function() {
         var xAxis = target.localTransform.x;
         target.rotateAround(target.position, xAxis, -this._offsetRoll * frameTime * Math.PI / 360);
 
+        /*
+         * If necessary: trigger `update` event.
+         * XXX This can economize rendering OPs.
+         */
+        if (this._moveForward === true || this._moveBackward === true || this._moveLeft === true
+            || this._moveRight === true || this._offsetPitch !== 0 || this._offsetRoll !== 0)
+        {
+            this.trigger('update');
+        }
+
         // Reset values to avoid lost of control.
 
         this._moveForward = this._moveBackward = this._moveLeft = this._moveRight = false;

{'commit': "diff --git a/src/plugin/GamepadControl.js b/src/plugin/GamepadControl.js\nindex 554caeff..b89b8dbe 100644\n--- a/src/plugin/GamepadControl.js\n+++ b/src/plugin/GamepadControl.js\n@@ -96,6 +96,8 @@ var GamepadControl = Base.extend(function() {\n     this._scanPressedGamepadButtons = this._scanPressedGamepadButtons.bind(this);\n     this._scanInclinedGamepadAxes = this._scanInclinedGamepadAxes.bind(this);\n \n+    this.update = this.update.bind(this);\n+\n     // If browser supports Gamepad API:\n     if (typeof navigator.getGamepads === 'function') {\n         this.init();\n@@ -181,6 +183,16 @@ var GamepadControl = Base.extend(function() {\n         var xAxis = target.localTransform.x;\n         target.rotateAround(target.position, xAxis, -this._offsetRoll * frameTime * Math.PI / 360);\n \n+        /*\n+         * If necessary: trigger `update` event.\n+         * XXX This can economize rendering OPs.\n+         */\n+        if (this._moveForward === true || this._moveBackward === true || this._moveLeft === true\n+            || this._moveRight === true || this._offsetPitch !== 0 || this._offsetRoll !== 0)\n+        {\n+            this.trigger('update');\n+        }\n+\n         // Reset values to avoid lost of control.\n \n         this._moveForward = this._moveBackward = this._moveLeft = this._moveRight = false;\n", 'commit_subject': 'Fix GamepadControl timeline param.', 'repo_name': 'pissang/claygl', 'committer': {'name': 'Samuel Tallet-Sabathé', 'email': 'a66144dcf70e35460e15f65d30ec1e0588d95dac@gmail.com', 'time_sec': '1526160900', 'tz_offset': '120', 'date': {'seconds': '1526160900'}}, 'commit_tree': 'bc4ab47ea6429f0ad7c806919f54d1045ac4f0b8', 'commit_message': 'Fix GamepadControl timeline param.\n', 'language_name': 'JavaScript', 'license': 'bsd-2-clause', 'hunks': "@@ -96,6 +96,8 @@ var GamepadControl = Base.extend(function() {\n     this._scanPressedGamepadButtons = this._scanPressedGamepadButtons.bind(this);\n     this._scanInclinedGamepadAxes = this._scanInclinedGamepadAxes.bind(this);\n \n+    this.update = this.update.bind(this);\n+\n     // If browser supports Gamepad API:\n     if (typeof navigator.getGamepads === 'function') {\n         this.init();\n@@ -181,6 +183,16 @@ var GamepadControl = Base.extend(function() {\n         var xAxis = target.localTransform.x;\n         target.rotateAround(target.position, xAxis, -this._offsetRoll * frameTime * Math.PI / 360);\n \n+        /*\n+         * If necessary: trigger `update` event.\n+         * XXX This can economize rendering OPs.\n+         */\n+        if (this._moveForward === true || this._moveBackward === true || this._moveLeft === true\n+            || this._moveRight === true || this._offsetPitch !== 0 || this._offsetRoll !== 0)\n+        {\n+            this.trigger('update');\n+        }\n+\n         // Reset values to avoid lost of control.\n \n         this._moveForward = this._moveBackward = this._moveLeft = this._moveRight = false;\n", 'addition_count': 12, 'deletion_count': 0, 'src_file': 'a/src/plugin/GamepadControl.js', 'tgt_file': 'b/src/plugin/GamepadControl.js', 'file_extension': '.js', 'before_file': "import Base from '../core/Base';\nimport Vector3 from '../math/Vector3';\nimport vendor from '../core/vendor';\n\n/**\n * Gamepad Control plugin.\n *\n * @constructor clay.plugin.GamepadControl\n *\n * @example\n *   init: function(app) {\n *     this._gamepadControl = new clay.plugin.GamepadControl({\n *         target: camera,\n *         onStandardGamepadReady: customCallback\n *     });\n *   },\n *\n *   loop: function(app) {\n *     this._gamepadControl.update(app.frameTime);\n *   }\n */\nvar GamepadControl = Base.extend(function() {\n\n    return /** @lends clay.plugin.GamepadControl# */ {\n\n        /**\n         * Scene node to control, mostly it is a camera.\n         *\n         * @type {clay.Node}\n         */\n        target: null,\n\n        /**\n         * Move speed.\n         *\n         * @type {number}\n         */\n        moveSpeed: 0.1,\n\n        /**\n         * Look around speed.\n         *\n         * @type {number}\n         */\n        lookAroundSpeed: 0.1,\n\n        /**\n         * Up axis.\n         *\n         * @type {clay.Vector3}\n         */\n        up: new Vector3(0, 1, 0),\n\n        /**\n         * Timeline.\n         *\n         * @type {clay.Timeline}\n         */\n        timeline: null,\n\n        /**\n         * Function to be called when a standard gamepad is ready to use.\n         *\n         * @type {function}\n         */\n        onStandardGamepadReady: function(gamepad){},\n\n        /**\n         * Function to be called when a gamepad is disconnected.\n         *\n         * @type {function}\n         */\n        onGamepadDisconnected: function(gamepad){},\n\n        // Private properties:\n\n        _moveForward: false,\n        _moveBackward: false,\n        _moveLeft: false,\n        _moveRight: false,\n\n        _offsetPitch: 0,\n        _offsetRoll: 0,\n\n        _connectedGamepadIndex: 0,\n        _standardGamepadAvailable: false,\n        _gamepadAxisThreshold: 0.3\n\n    };\n\n}, function() {\n\n    this._checkGamepadCompatibility = this._checkGamepadCompatibility.bind(this);\n    this._disconnectGamepad = this._disconnectGamepad.bind(this);\n    this._getStandardGamepad = this._getStandardGamepad.bind(this);\n    this._scanPressedGamepadButtons = this._scanPressedGamepadButtons.bind(this);\n    this._scanInclinedGamepadAxes = this._scanInclinedGamepadAxes.bind(this);\n\n    // If browser supports Gamepad API:\n    if (typeof navigator.getGamepads === 'function') {\n        this.init();\n    }\n\n},\n/** @lends clay.plugin.GamepadControl.prototype */\n{\n    /**\n     * Init. control.\n     */\n    init: function() {\n\n        /**\n         * When user begins to interact with connected gamepad:\n         *\n         * @see https://w3c.github.io/gamepad/#dom-gamepadevent\n         */\n        vendor.addEventListener(window, 'gamepadconnected', this._checkGamepadCompatibility);\n\n        if (this.timeline) {\n            this.timeline.on('frame', this.update);\n        }\n\n        vendor.addEventListener(window, 'gamepaddisconnected', this._disconnectGamepad);\n\n    },\n\n    /**\n     * Dispose control.\n     */\n    dispose: function() {\n\n        vendor.removeEventListener(window, 'gamepadconnected', this._checkGamepadCompatibility);\n\n        if (this.timeline) {\n            this.timeline.off('frame', this.update);\n        }\n\n        vendor.removeEventListener(window, 'gamepaddisconnected', this._disconnectGamepad);\n\n    },\n\n    /**\n     * Control's update. Should be invoked every frame.\n     *\n     * @param {number} frameTime Frame time.\n     */\n    update: function (frameTime) {\n\n        if (!this._standardGamepadAvailable) {\n            return;\n        }\n\n        this._scanPressedGamepadButtons();\n        this._scanInclinedGamepadAxes();\n\n        // Update target depending on user input.\n\n        var target = this.target;\n\n        var position = this.target.position;\n        var xAxis = target.localTransform.x.normalize();\n        var zAxis = target.localTransform.z.normalize();\n\n        var moveSpeed = this.moveSpeed * frameTime / 20;\n\n        if (this._moveForward) {\n            // Opposite direction of z.\n            position.scaleAndAdd(zAxis, -moveSpeed);\n        }\n        if (this._moveBackward) {\n            position.scaleAndAdd(zAxis, moveSpeed);\n        }\n        if (this._moveLeft) {\n            position.scaleAndAdd(xAxis, -moveSpeed);\n        }\n        if (this._moveRight) {\n            position.scaleAndAdd(xAxis, moveSpeed);\n        }\n\n        target.rotateAround(target.position, this.up, -this._offsetPitch * frameTime * Math.PI / 360);\n        var xAxis = target.localTransform.x;\n        target.rotateAround(target.position, xAxis, -this._offsetRoll * frameTime * Math.PI / 360);\n\n        // Reset values to avoid lost of control.\n\n        this._moveForward = this._moveBackward = this._moveLeft = this._moveRight = false;\n        this._offsetPitch = this._offsetRoll = 0;\n\n    },\n\n    // Private methods:\n\n    _checkGamepadCompatibility: function(event) {\n\n        /**\n         * If connected gamepad has a **standard** layout:\n         *\n         * @see https://w3c.github.io/gamepad/#remapping about standard.\n         */\n        if (event.gamepad.mapping === 'standard') {\n\n            this._standardGamepadIndex = event.gamepad.index;\n            this._standardGamepadAvailable = true;\n\n            this.onStandardGamepadReady(event.gamepad);\n\n        }\n\n    },\n\n    _disconnectGamepad: function(event) {\n\n        this._standardGamepadAvailable = false;\n\n        this.onGamepadDisconnected(event.gamepad);\n\n    },\n\n    _getStandardGamepad: function() {\n\n        return navigator.getGamepads()[this._standardGamepadIndex];\n\n    },\n\n    _scanPressedGamepadButtons: function() {\n\n        var gamepadButtons = this._getStandardGamepad().buttons;\n\n        // For each gamepad button:\n        for (var gamepadButtonId = 0; gamepadButtonId < gamepadButtons.length; gamepadButtonId++) {\n\n            // Get user input.\n            var gamepadButton = gamepadButtons[gamepadButtonId];\n\n            if (gamepadButton.pressed) {\n\n                switch (gamepadButtonId) {\n\n                    // D-pad Up\n                    case 12:\n                        this._moveForward = true;\n                        break;\n\n                    // D-pad Down\n                    case 13:\n                        this._moveBackward = true;\n                        break;\n\n                    // D-pad Left\n                    case 14:\n                        this._moveLeft = true;\n                        break;\n\n                    // D-pad Right\n                    case 15:\n                        this._moveRight = true;\n                        break;\n\n                }\n\n            }\n\n        }\n\n    },\n\n    _scanInclinedGamepadAxes: function() {\n\n        var gamepadAxes = this._getStandardGamepad().axes;\n\n        // For each gamepad axis:\n        for (var gamepadAxisId = 0; gamepadAxisId < gamepadAxes.length; gamepadAxisId++) {\n\n            // Get user input.\n            var gamepadAxis = gamepadAxes[gamepadAxisId];\n\n            // XXX We use a threshold because axes are never neutral.\n            if (Math.abs(gamepadAxis) > this._gamepadAxisThreshold) {\n\n                switch (gamepadAxisId) {\n\n                    // Left stick X±\n                    case 0:\n                        this._moveLeft = gamepadAxis < 0;\n                        this._moveRight = gamepadAxis > 0;\n                        break;\n\n                    // Left stick Y±\n                    case 1:\n                        this._moveForward = gamepadAxis < 0;\n                        this._moveBackward = gamepadAxis > 0;\n                        break;\n\n                    // Right stick X±\n                    case 2:\n                        this._offsetPitch += gamepadAxis * this.lookAroundSpeed;\n                        break;\n\n                    // Right stick Y±\n                    case 3:\n                        this._offsetRoll += gamepadAxis * this.lookAroundSpeed;\n                        break;\n\n                }\n\n            }\n\n        }\n\n    }\n\n});\n\nexport default GamepadControl;\n", 'message': 'Fix GamepadControl timeline param.\n', 'author_name': 'Samuel Tallet-Sabathé', 'lang': 'js'}