From 0fb4e700b8b98990adc86b83192db1fbc0a25b7b Mon Sep 17 00:00:00 2001 From: machenmusik Date: Mon, 20 Feb 2017 11:29:59 -0500 Subject: [PATCH 01/20] add axes mapping, axismove handler, as well as tests ... details follow add axes mapping, axismove handler and emulated property, as well as tests add emulated property to docs accommodate temporary Nightly issue where Touch is supported but has lowercased handedness in id string Pass along changed event with button state, using the buttom mapping for convenience. pass along axis changed flags from tracked-controls, and check them in oculus-touch-controls and vive-controls to avoid spurious axis moved events add update comment per discussion on PR fix typo for compatibility, if no changed array for axismove event, treat as changed map to forEach, per discussion on PR remove emulated property per discussion change button names so events are lowercase --- docs/components/oculus-touch-controls.md | 41 +++-- docs/components/vive-controls.md | 29 +-- src/components/hand-controls.js | 32 ++-- src/components/oculus-touch-controls.js | 167 +++++++++++------- src/components/tracked-controls.js | 9 +- src/components/vive-controls.js | 82 +++++---- .../components/oculus-touch-controls.test.js | 101 +++++++++-- tests/components/tracked-controls.test.js | 16 ++ tests/components/vive-controls.test.js | 93 ++++++++-- 9 files changed, 404 insertions(+), 166 deletions(-) diff --git a/docs/components/oculus-touch-controls.md b/docs/components/oculus-touch-controls.md index 106e3f5395d..fe17bfffe5d 100644 --- a/docs/components/oculus-touch-controls.md +++ b/docs/components/oculus-touch-controls.md @@ -34,34 +34,43 @@ mappings, events, and a Touch controller model. | triggerup | Trigger released. | | triggertouchstart | Trigger touched. | | triggertouchend | Trigger no longer touched. | +| triggerchanged | Trigger changed. | | thumbstickdown | Thumbstick pressed. | | thumbstickup | Thumbstick released. | | thumbsticktouchstart | Thumbstick touched. | | thumbsticktouchend | Thumbstick no longer touched. | +| thumbstickchanged | Thumbstick changed. | | gripdown | Grip button pressed. | | gripup | Grip button released. | | griptouchstart | Grip button touched. | | griptouchend | Grip button no longer touched. | -| Adown | A button pressed. | -| Aup | A button released. | -| Atouchstart | A button touched. | -| Atouchend | A button no longer touched. | -| Bdown | B button pressed. | -| Bup | B button released. | -| Btouchstart | B button touched. | -| Btouchend | B button no longer touched. | -| Xdown | X button pressed. | -| Xup | X button released. | -| Xtouchstart | X button touched. | -| Xtouchend | X button no longer touched. | -| Ydown | Y button pressed. | -| Yup | Y button released. | -| Ytouchstart | Y button touched. | -| Ytouchend | Y button no longer touched. | +| gripchanged | Grip button changed. | +| abuttondown | A button pressed. | +| abuttonup | A button released. | +| abuttontouchstart | A button touched. | +| abuttontouchend | A button no longer touched. | +| abuttonchanged | A button changed. | +| bbuttondown | B button pressed. | +| bbuttonup | B button released. | +| bbuttontouchstart | B button touched. | +| bbuttontouchend | B button no longer touched. | +| bbuttonchanged | B button changed. | +| xbuttondown | X button pressed. | +| xbuttonup | X button released. | +| xbuttontouchstart | X button touched. | +| xbuttontouchend | X button no longer touched. | +| xbuttonchanged | X button changed. | +| ybuttondown | Y button pressed. | +| ybuttonup | Y button released. | +| ybuttontouchstart | Y button touched. | +| ybuttontouchend | Y button no longer touched. | +| ybuttonchanged | Y button changed. | | menudown | Menu button pressed. | | menuup | Menu button released. | +| menuchanged | Menu button changed. | | systemdown | System button pressed. | | systemup | System button released. | +| systemchanged | System button changed. | ## Assets diff --git a/docs/components/vive-controls.md b/docs/components/vive-controls.md index 790f8098ebf..96cfd16c8e0 100644 --- a/docs/components/vive-controls.md +++ b/docs/components/vive-controls.md @@ -31,18 +31,23 @@ buttons (trigger, grip, menu, system) and trackpad. ## Events -| Event Name | Description | -| ---------- | ----------- | -| gripdown | Grip button pressed. | -| gripup | Grip button released. | -| menudown | Menu button pressed. | -| menuup | Menu button released. | -| systemdown | System button pressed. | -| systemup | System button released. | -| trackpaddown | Trackpad pressed. | -| trackpadup | Trackpad released. | -| triggerdown | Trigger pressed. | -| triggerup | Trigger released. | +| Event Name | Description | +| ---------- | ----------- | +| gripdown | Grip button pressed. | +| gripup | Grip button released. | +| gripchanged | Grip button changed. | +| menudown | Menu button pressed. | +| menuup | Menu button released. | +| menuchanged | Menu button changed. | +| systemdown | System button pressed. | +| systemup | System button released. | +| systemchanged | System button changed. | +| trackpaddown | Trackpad pressed. | +| trackpadup | Trackpad released. | +| trackpadchanged | Trackpad button changed. | +| triggerdown | Trigger pressed. | +| triggerup | Trigger released. | +| triggerchanged | Trigger changed. | ## Assets diff --git a/src/components/hand-controls.js b/src/components/hand-controls.js index 0bea53f0e8a..b8fbdc49da7 100644 --- a/src/components/hand-controls.js +++ b/src/components/hand-controls.js @@ -99,14 +99,14 @@ module.exports.Component = registerComponent('hand-controls', { el.addEventListener('griptouchend', this.onGripTouchEnd); el.addEventListener('thumbstickdown', this.onThumbstickDown); el.addEventListener('thumbstickup', this.onThumbstickUp); - el.addEventListener('Atouchstart', this.onAorXTouchStart); - el.addEventListener('Atouchend', this.onAorXTouchEnd); - el.addEventListener('Btouchstart', this.onBorYTouchStart); - el.addEventListener('Btouchend', this.onBorYTouchEnd); - el.addEventListener('Xtouchstart', this.onAorXTouchStart); - el.addEventListener('Xtouchend', this.onAorXTouchEnd); - el.addEventListener('Ytouchstart', this.onBorYTouchStart); - el.addEventListener('Ytouchend', this.onBorYTouchEnd); + el.addEventListener('abuttontouchstart', this.onAorXTouchStart); + el.addEventListener('abuttontouchend', this.onAorXTouchEnd); + el.addEventListener('bbuttontouchstart', this.onBorYTouchStart); + el.addEventListener('bbuttontouchend', this.onBorYTouchEnd); + el.addEventListener('xbuttontouchstart', this.onAorXTouchStart); + el.addEventListener('xbuttontouchend', this.onAorXTouchEnd); + el.addEventListener('ybuttontouchstart', this.onBorYTouchStart); + el.addEventListener('ybuttontouchend', this.onBorYTouchEnd); el.addEventListener('surfacetouchstart', this.onSurfaceTouchStart); el.addEventListener('surfacetouchend', this.onSurfaceTouchEnd); }, @@ -127,14 +127,14 @@ module.exports.Component = registerComponent('hand-controls', { el.removeEventListener('griptouchend', this.onGripTouchEnd); el.removeEventListener('thumbstickdown', this.onThumbstickDown); el.removeEventListener('thumbstickup', this.onThumbstickUp); - el.removeEventListener('Atouchstart', this.onAorXTouchStart); - el.removeEventListener('Atouchend', this.onAorXTouchEnd); - el.removeEventListener('Btouchstart', this.onBorYTouchStart); - el.removeEventListener('Btouchend', this.onBorYTouchEnd); - el.removeEventListener('Xtouchstart', this.onAorXTouchStart); - el.removeEventListener('Xtouchend', this.onAorXTouchEnd); - el.removeEventListener('Ytouchstart', this.onBorYTouchStart); - el.removeEventListener('Ytouchend', this.onBorYTouchEnd); + el.removeEventListener('abuttontouchstart', this.onAorXTouchStart); + el.removeEventListener('abuttontouchend', this.onAorXTouchEnd); + el.removeEventListener('bbuttontouchstart', this.onBorYTouchStart); + el.removeEventListener('bbuttontouchend', this.onBorYTouchEnd); + el.removeEventListener('xbuttontouchstart', this.onAorXTouchStart); + el.removeEventListener('xbuttontouchend', this.onAorXTouchEnd); + el.removeEventListener('ybuttontouchstart', this.onBorYTouchStart); + el.removeEventListener('ybuttontouchend', this.onBorYTouchEnd); el.removeEventListener('surfacetouchstart', this.onSurfaceTouchStart); el.removeEventListener('surfacetouchend', this.onSurfaceTouchEnd); }, diff --git a/src/components/oculus-touch-controls.js b/src/components/oculus-touch-controls.js index d8a25b731b5..811d3634f55 100644 --- a/src/components/oculus-touch-controls.js +++ b/src/components/oculus-touch-controls.js @@ -1,6 +1,7 @@ var registerComponent = require('../core/component').registerComponent; var bind = require('../utils/bind'); var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; +var getGamepadsByPrefix = require('../utils/tracked-controls').getGamepadsByPrefix; var TOUCH_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/oculus/oculus-touch-controller-'; var TOUCH_CONTROLLER_MODEL_OBJ_URL_L = TOUCH_CONTROLLER_MODEL_BASE_URL + 'left.obj'; @@ -28,46 +29,48 @@ module.exports.Component = registerComponent('oculus-touch-controls', { buttonColor: {type: 'color', default: '#999'}, // Off-white. buttonTouchColor: {type: 'color', default: '#8AB'}, buttonHighlightColor: {type: 'color', default: '#2DF'}, // Light blue. - model: { default: true }, + model: {default: true}, rotationOffset: {default: 0} // no default offset; -999 is sentinel value to auto-determine based on hand }, // buttonId - // 0 - thumbstick - // 1 - trigger ( intensity value from 0.5 to 1 ) - // 2 - grip - // 3 - menu ( dispatch but better for menu options ) - // 4 - system ( never dispatched on this layer ) + // 0 - thumbstick (which has separate axismove / thumbstickmoved events) + // 1 - trigger (with analog value, which goes up to 1) + // 2 - grip (with analog value, which goes up to 1) + // 3 - X (left) or A (right) + // 4 - Y (left) or B (right) + // 5 - surface (touch only) mapping: { 'left': { - axis0: 'thumbstick', - axis1: 'thumbstick', + axes: {'thumbstick': [0, 1]}, button0: 'thumbstick', button1: 'trigger', button2: 'grip', - button3: 'X', - button4: 'Y', + button3: 'xbutton', + button4: 'ybutton', button5: 'surface' }, 'right': { - axis0: 'thumbstick', - axis1: 'thumbstick', + axes: {'thumbstick': [0, 1]}, button0: 'thumbstick', button1: 'trigger', button2: 'grip', - button3: 'A', - button4: 'B', + button3: 'abutton', + button4: 'bbutton', button5: 'surface' } }, + // Use these labels for detail on axis events such as thumbstickmoved. + // e.g. for thumbstickmoved detail, the first axis returned is labeled x, and the second is labeled y. + axisLabels: ['x', 'y', 'z', 'w'], + bindMethods: function () { this.onModelLoaded = bind(this.onModelLoaded, this); this.onControllersUpdate = bind(this.onControllersUpdate, this); this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); - this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); - this.onGamepadConnected = bind(this.onGamepadConnected, this); - this.onGamepadDisconnected = bind(this.onGamepadDisconnected, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + this.onGamepadConnectionEvent = bind(this.onGamepadConnectionEvent, this); }, init: function () { @@ -84,6 +87,7 @@ module.exports.Component = registerComponent('oculus-touch-controls', { this.previousButtonValues = {}; this.bindMethods(); this.isControllerPresent = isControllerPresent; // to allow mock + this.getGamepadsByPrefix = getGamepadsByPrefix; // to allow mock }, addEventListeners: function () { @@ -93,6 +97,7 @@ module.exports.Component = registerComponent('oculus-touch-controls', { el.addEventListener('buttonup', this.onButtonUp); el.addEventListener('touchstart', this.onButtonTouchStart); el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('axismove', this.onAxisMoved); el.addEventListener('model-loaded', this.onModelLoaded); }, @@ -103,43 +108,42 @@ module.exports.Component = registerComponent('oculus-touch-controls', { el.removeEventListener('buttonup', this.onButtonUp); el.removeEventListener('touchstart', this.onButtonTouchStart); el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('axismove', this.onAxisMoved); el.removeEventListener('model-loaded', this.onModelLoaded); }, checkIfControllerPresent: function () { var data = this.data; - var isPresent = this.isControllerPresent(this.el.sceneEl, GAMEPAD_ID_PREFIX, { hand: data.hand }); + var isPresent = false; + var whichControllers; + // Find which controller matches both prefix and hand. + whichControllers = (this.getGamepadsByPrefix(GAMEPAD_ID_PREFIX) || []) + .filter(function (gamepad) { return gamepad.hand === data.hand; }); + if (whichControllers && whichControllers.length) { isPresent = true; } if (isPresent === this.controllerPresent) { return; } this.controllerPresent = isPresent; if (isPresent) { - this.injectTrackedControls(); // inject track-controls + // Inject with specific gamepad id, if provided. This works around a temporary issue + // where Chromium uses `Oculus Touch (Right)` but Firefox uses `Oculus Touch (right)`. + this.injectTrackedControls(whichControllers[0]); this.addEventListeners(); - } else { - this.removeEventListeners(); - } - }, - - onGamepadConnected: function (evt) { - // for now, don't disable controller update listening, due to - // apparent issue with FF Nightly only sending one event and seeing one controller; - // this.everGotGamepadEvent = true; - // this.removeControllersUpdateListener(); - this.checkIfControllerPresent(); + } else { this.removeEventListeners(); } }, - onGamepadDisconnected: function (evt) { - // for now, don't disable controller update listening, due to - // apparent issue with FF Nightly only sending one event and seeing one controller; - // this.everGotGamepadEvent = true; - // this.removeControllersUpdateListener(); + onGamepadConnectionEvent: function (evt) { + this.everGotGamepadEvent = true; + // Due to an apparent bug in FF Nightly + // where only one gamepadconnected / disconnected event is fired, + // which makes it difficult to handle in individual controller entities, + // we no longer remove the controllersupdate listener as a result. this.checkIfControllerPresent(); }, play: function () { this.checkIfControllerPresent(); - window.addEventListener('gamepadconnected', this.onGamepadConnected, false); - window.addEventListener('gamepaddisconnected', this.onGamepadDisconnected, false); this.addControllersUpdateListener(); + window.addEventListener('gamepadconnected', this.onGamepadConnectionEvent, false); + window.addEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false); }, pause: function () { @@ -147,6 +151,9 @@ module.exports.Component = registerComponent('oculus-touch-controls', { window.removeEventListener('gamepaddisconnected', this.onGamepadDisconnected, false); this.removeControllersUpdateListener(); this.removeEventListeners(); + this.removeControllersUpdateListener(); + window.removeEventListener('gamepadconnected', this.onGamepadConnectionEvent, false); + window.removeEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false); }, updateControllerModel: function () { @@ -162,14 +169,14 @@ module.exports.Component = registerComponent('oculus-touch-controls', { this.el.setAttribute('obj-model', {obj: objUrl, mtl: mtlUrl}); }, - injectTrackedControls: function () { + injectTrackedControls: function (gamepad) { var el = this.el; var data = this.data; var isRightHand = data.hand === 'right'; - - // since each hand is named differently, avoid enumeration + // Inject with specific gamepad id, if provided. This works around a temporary issue + // where Chromium uses `Oculus Touch (Right)` but Firefox uses `Oculus Touch (right)`. el.setAttribute('tracked-controls', { - id: isRightHand ? 'Oculus Touch (Right)' : 'Oculus Touch (Left)', + id: gamepad ? gamepad.id : isRightHand ? 'Oculus Touch (Right)' : 'Oculus Touch (Left)', controller: 0, rotationOffset: data.rotationOffset !== -999 ? data.rotationOffset : isRightHand ? -90 : 90 }); @@ -185,11 +192,15 @@ module.exports.Component = registerComponent('oculus-touch-controls', { }, onControllersUpdate: function () { - if (!this.everGotGamepadEvent) { this.checkIfControllerPresent(); } + // Due to an apparent bug in FF Nightly + // where only one gamepadconnected / disconnected event is fired, + // which makes it difficult to handle in individual controller entities, + // we no longer remove the controllersupdate listener when we get a gamepad event. + this.checkIfControllerPresent(); }, - // currently, browser bugs prevent capacitive touch events from firing on trigger and grip; - // however those have analog values, and this (below button-down values) can be used to fake them + // Currently, browser bugs prevent capacitive touch events from firing on trigger and grip; + // however those have analog values, and this (below button-down values) can be used to fake them. isEmulatedTouchEvent: function (analogValue) { return analogValue && (analogValue >= EMULATED_TOUCH_THRESHOLD); }, @@ -201,24 +212,33 @@ module.exports.Component = registerComponent('oculus-touch-controls', { var analogValue; var isEmulatedTouch; - // at the moment, if trigger or grip, - // touch events aren't happening (touched is stuck true); - // synthesize touch events from very low analog values - if (button !== 'trigger' && button !== 'grip') { return; } - analogValue = evt.detail.state.value; - isPreviousValueEmulatedTouch = this.isEmulatedTouchEvent(this.previousButtonValues[button]); - this.previousButtonValues[button] = analogValue; - isEmulatedTouch = this.isEmulatedTouchEvent(analogValue); - if (isEmulatedTouch !== isPreviousValueEmulatedTouch) { - (isEmulatedTouch ? this.onButtonTouchStart : this.onButtonTouchEnd)(evt); - } - if (!buttonMeshes) { return; } - if (button === 'trigger' && buttonMeshes.trigger) { - buttonMeshes.trigger.rotation.x = -analogValue * (Math.PI / 24); + if (!button) { return; } + + if (button === 'trigger' || button === 'grip') { + // At the moment, if trigger or grip, + // touch events aren't happening (touched is stuck true); + // synthesize touch events from very low analog values. + analogValue = evt.detail.state.value; + isPreviousValueEmulatedTouch = this.isEmulatedTouchEvent(this.previousButtonValues[button]); + this.previousButtonValues[button] = analogValue; + isEmulatedTouch = this.isEmulatedTouchEvent(analogValue); + if (isEmulatedTouch !== isPreviousValueEmulatedTouch) { + (isEmulatedTouch ? this.onButtonTouchStart : this.onButtonTouchEnd)(evt); + } } - if (button === 'grip' && buttonMeshes.grip) { - buttonMeshes.grip.rotation.y = (this.data.hand === 'left' ? -1 : 1) * analogValue * (Math.PI / 60); + + // Update trigger and/or grip meshes, if any. + if (buttonMeshes) { + if (button === 'trigger' && buttonMeshes.trigger) { + buttonMeshes.trigger.rotation.x = -analogValue * (Math.PI / 24); + } + if (button === 'grip' && buttonMeshes.grip) { + buttonMeshes.grip.rotation.y = (this.data.hand === 'left' ? -1 : 1) * analogValue * (Math.PI / 60); + } } + + // Pass along changed event with button state, using the buttom mapping for convenience. + this.el.emit(button + 'changed', evt.detail.state); }, onModelLoaded: function (evt) { @@ -232,10 +252,10 @@ module.exports.Component = registerComponent('oculus-touch-controls', { buttonMeshes.grip = controllerObject3D.getObjectByName(leftHand ? 'buttonHand_oculus-touch-controller-left.004' : 'buttonHand_oculus-touch-controller-right.005'); buttonMeshes.thumbstick = controllerObject3D.getObjectByName(leftHand ? 'stick_oculus-touch-controller-left.007' : 'stick_oculus-touch-controller-right.004'); buttonMeshes.trigger = controllerObject3D.getObjectByName(leftHand ? 'buttonTrigger_oculus-touch-controller-left.005' : 'buttonTrigger_oculus-touch-controller-right.006'); - buttonMeshes.X = controllerObject3D.getObjectByName('buttonX_oculus-touch-controller-left.002'); - buttonMeshes.A = controllerObject3D.getObjectByName('buttonA_oculus-touch-controller-right.002'); - buttonMeshes.Y = controllerObject3D.getObjectByName('buttonY_oculus-touch-controller-left.001'); - buttonMeshes.B = controllerObject3D.getObjectByName('buttonB_oculus-touch-controller-right.003'); + buttonMeshes.xbutton = controllerObject3D.getObjectByName('buttonX_oculus-touch-controller-left.002'); + buttonMeshes.abutton = controllerObject3D.getObjectByName('buttonA_oculus-touch-controller-right.002'); + buttonMeshes.ybutton = controllerObject3D.getObjectByName('buttonY_oculus-touch-controller-left.001'); + buttonMeshes.bbutton = controllerObject3D.getObjectByName('buttonB_oculus-touch-controller-right.003'); // Offset pivot point controllerObject3D.position = PIVOT_OFFSET; @@ -254,6 +274,25 @@ module.exports.Component = registerComponent('oculus-touch-controls', { this.updateModel(buttonName, evtName); }, + onAxisMoved: function (evt) { + var self = this; + var axesMapping = this.mapping[this.data.hand].axes; + // In theory, it might be better to use mapping from axis to control. + // In practice, it is not clear whether the additional overhead is worthwhile, + // and if we did grouping of axes, we really need de-duplication there. + Object.keys(axesMapping).forEach(function (key) { + var value = axesMapping[key]; + var detail = {}; + var changed = !evt.detail.changed; + if (!changed) { value.forEach(function (axisNumber) { changed |= evt.detail.changed[axisNumber]; }); } + if (changed) { + value.forEach(function (axisNumber) { detail[self.axisLabels[axisNumber]] = evt.detail.axis[axisNumber]; }); + self.el.emit(key + 'moved', detail); + // If we updated the model based on axis values, that call would go here. + } + }); + }, + updateModel: function (buttonName, evtName) { var i; if (Array.isArray(buttonName)) { diff --git a/src/components/tracked-controls.js b/src/components/tracked-controls.js index caaabae3d80..83af4e441c4 100644 --- a/src/components/tracked-controls.js +++ b/src/components/tracked-controls.js @@ -244,18 +244,17 @@ module.exports.Component = registerComponent('tracked-controls', { var controllerAxes = this.controller.axes; var i; var previousAxis = this.axis; + var changedAxes = []; // Check if axis changed. for (i = 0; i < controllerAxes.length; ++i) { - if (previousAxis[i] !== controllerAxes[i]) { - changed = true; - break; - } + changedAxes.push(previousAxis[i] !== controllerAxes[i]); + if (changedAxes[i]) { changed = true; } } if (!changed) { return false; } this.axis = controllerAxes.slice(); - this.el.emit('axismove', {axis: this.axis}); + this.el.emit('axismove', {axis: this.axis, changed: changedAxes}); return true; }, diff --git a/src/components/vive-controls.js b/src/components/vive-controls.js index 71eabcdd802..aade199675e 100644 --- a/src/components/vive-controls.js +++ b/src/components/vive-controls.js @@ -29,8 +29,7 @@ module.exports.Component = registerComponent('vive-controls', { // 3 - menu ( dispatch but better for menu options ) // 4 - system ( never dispatched on this layer ) mapping: { - axis0: 'trackpad', - axis1: 'trackpad', + axes: {'trackpad': [0, 1]}, button0: 'trackpad', button1: 'trigger', button2: 'grip', @@ -38,6 +37,10 @@ module.exports.Component = registerComponent('vive-controls', { button4: 'system' }, + // Use these labels for detail on axis events such as thumbstickmoved. + // e.g. for thumbstickmoved detail, the first axis returned is labeled x, and the second is labeled y. + axisLabels: ['x', 'y', 'z', 'w'], + bindMethods: function () { this.onModelLoaded = bind(this.onModelLoaded, this); this.onControllersUpdate = bind(this.onControllersUpdate, this); @@ -45,6 +48,8 @@ module.exports.Component = registerComponent('vive-controls', { this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); this.onGamepadConnected = bind(this.onGamepadConnected, this); this.onGamepadDisconnected = bind(this.onGamepadDisconnected, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + this.onGamepadConnectionEvent = bind(this.onGamepadConnectionEvent, this); }, init: function () { @@ -87,46 +92,40 @@ module.exports.Component = registerComponent('vive-controls', { checkIfControllerPresent: function () { var data = this.data; - var controller = data.hand === 'right' ? 0 : data.hand === 'left' ? 1 : 2; - var isPresent = this.isControllerPresent(this.el.sceneEl, GAMEPAD_ID_PREFIX, { index: controller }); + // Once OpenVR / SteamVR return correct hand data in the supporting browsers, we can use hand property. + // var isPresent = this.isControllerPresent(this.el.sceneEl, GAMEPAD_ID_PREFIX, { hand: data.hand }); + // Until then, use hardcoded index. + var controllerIndex = data.hand === 'right' ? 0 : data.hand === 'left' ? 1 : 2; + var isPresent = this.isControllerPresent(this.el.sceneEl, GAMEPAD_ID_PREFIX, { index: controllerIndex }); if (isPresent === this.controllerPresent) { return; } this.controllerPresent = isPresent; if (isPresent) { - this.injectTrackedControls(); // inject track-controls + this.injectTrackedControls(); this.addEventListeners(); - } else { - this.removeEventListeners(); - } - }, - - onGamepadConnected: function (evt) { - // for now, don't disable controller update listening, due to - // apparent issue with FF Nightly only sending one event and seeing one controller; - // this.everGotGamepadEvent = true; - // this.removeControllersUpdateListener(); - this.checkIfControllerPresent(); + } else { this.removeEventListeners(); } }, - onGamepadDisconnected: function (evt) { - // for now, don't disable controller update listening, due to - // apparent issue with FF Nightly only sending one event and seeing one controller; - // this.everGotGamepadEvent = true; - // this.removeControllersUpdateListener(); + onGamepadConnectionEvent: function (evt) { + this.everGotGamepadEvent = true; + // Due to an apparent bug in FF Nightly + // where only one gamepadconnected / disconnected event is fired, + // which makes it difficult to handle in individual controller entities, + // we no longer remove the controllersupdate listener as a result. this.checkIfControllerPresent(); }, play: function () { this.checkIfControllerPresent(); - window.addEventListener('gamepadconnected', this.onGamepadConnected, false); - window.addEventListener('gamepaddisconnected', this.onGamepadDisconnected, false); this.addControllersUpdateListener(); + window.addEventListener('gamepadconnected', this.onGamepadConnectionEvent, false); + window.addEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false); }, pause: function () { - window.removeEventListener('gamepadconnected', this.onGamepadConnected, false); - window.removeEventListener('gamepaddisconnected', this.onGamepadDisconnected, false); - this.removeControllersUpdateListener(); this.removeEventListeners(); + this.removeControllersUpdateListener(); + window.removeEventListener('gamepadconnected', this.onGamepadConnectionEvent, false); + window.removeEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false); }, injectTrackedControls: function () { @@ -159,9 +158,16 @@ module.exports.Component = registerComponent('vive-controls', { var button = this.mapping['button' + evt.detail.id]; var buttonMeshes = this.buttonMeshes; var value; - if (!button || !buttonMeshes || button !== 'trigger') { return; } - value = evt.detail.state.value; - buttonMeshes.trigger.rotation.x = -value * (Math.PI / 12); + if (!button) { return; } + + // Update button mesh, if any. + if (buttonMeshes && button === 'trigger') { + value = evt.detail.state.value; + buttonMeshes.trigger.rotation.x = -value * (Math.PI / 12); + } + + // Pass along changed event with button state, using button mapping for convenience. + this.el.emit(button + 'changed', evt.detail.state); }, onModelLoaded: function (evt) { @@ -182,8 +188,22 @@ module.exports.Component = registerComponent('vive-controls', { }, onAxisMoved: function (evt) { - if (evt.detail.axis[0] === 0 && evt.detail.axis[1] === 0) { return; } - this.el.emit('trackpadmoved', { x: evt.detail.axis[0], y: evt.detail.axis[1] }); + var self = this; + var axesMapping = this.mapping.axes; + // In theory, it might be better to use mapping from axis to control. + // In practice, it is not clear whether the additional overhead is worthwhile, + // and if we did grouping of axes, we really need de-duplication there. + Object.keys(axesMapping).forEach(function (key) { + var value = axesMapping[key]; + var detail = {}; + var changed = !evt.detail.changed; + if (!changed) { value.forEach(function (axisNumber) { changed |= evt.detail.changed[axisNumber]; }); } + if (changed) { + value.forEach(function (axisNumber) { detail[self.axisLabels[axisNumber]] = evt.detail.axis[axisNumber]; }); + self.el.emit(key + 'moved', detail); + // If we updated the model based on axis values, that call would go here. + } + }); }, onButtonEvent: function (id, evtName) { diff --git a/tests/components/oculus-touch-controls.test.js b/tests/components/oculus-touch-controls.test.js index 9a056dbdb82..0cb662d26dd 100644 --- a/tests/components/oculus-touch-controls.test.js +++ b/tests/components/oculus-touch-controls.test.js @@ -1,7 +1,9 @@ -/* global assert, process, setup, suite, test */ +/* global assert, process, setup, suite, test, CustomEvent, Event */ var entityFactory = require('../helpers').entityFactory; var controllerComponentName = 'oculus-touch-controls'; +var emulatedControllers = [{id: 'Oculus Touch (Left)', hand: 'left'}, {id: 'Oculus Touch (right)', hand: 'right'}]; + suite(controllerComponentName, function () { setup(function (done) { var el = this.el = entityFactory(); @@ -9,6 +11,9 @@ suite(controllerComponentName, function () { el.addEventListener('loaded', function () { var controllerComponent = el.components[controllerComponentName]; controllerComponent.isControllerPresent = function () { return controllerComponent.isControllerPresentMockValue; }; + controllerComponent.getGamepadsByPrefix = function () { + return controllerComponent.isControllerPresentMockValue ? emulatedControllers : null; + }; done(); }); }); @@ -95,8 +100,7 @@ suite(controllerComponentName, function () { var controllerComponent = el.components[controllerComponentName]; var addEventListenersSpy = this.sinon.spy(controllerComponent, 'addEventListeners'); var injectTrackedControlsSpy = this.sinon.spy(controllerComponent, 'injectTrackedControls'); - var removeEventListenersSpy = this.sinon.spy(controllerComponent, 'removeEventListeners'); - // mock isControllerPresent to return true + // mock isControllerPresent to return false controllerComponent.isControllerPresentMockValue = false; // pretend we've looked before controllerComponent.controllerPresent = true; @@ -110,26 +114,99 @@ suite(controllerComponentName, function () { }); }); - suite.skip('onGamepadConnected / Disconnected', function () { - test('if we get onGamepadConnected or onGamepadDisconnected, remove periodic change listener and check if present', function () { + suite('axismove', function () { + var name = 'thumbstick'; + test('if we get axismove, emit ' + name + 'moved', function (done) { + var el = this.el; + var controllerComponent = el.components[controllerComponentName]; + var evt; + // mock isControllerPresent to return true + controllerComponent.isControllerPresentMockValue = true; + // do the check + controllerComponent.checkIfControllerPresent(); + // install event handler listening for thumbstickmoved + this.el.addEventListener(name + 'moved', function (evt) { + assert.equal(evt.detail.x, 0.1); + assert.equal(evt.detail.y, 0.2); + assert.ok(evt.detail); + done(); + }); + // emit axismove + evt = new CustomEvent('axismove', {'detail': {axis: [0.1, 0.2], changed: [true, false]}}); + this.el.dispatchEvent(evt); + }); + + test('if we get axismove with no changes, do not emit ' + name + 'moved', function (done) { + var el = this.el; + var controllerComponent = el.components[controllerComponentName]; + var evt; + // mock isControllerPresent to return true + controllerComponent.isControllerPresentMockValue = true; + // do the check + controllerComponent.checkIfControllerPresent(); + // install event handler listening for thumbstickmoved + this.el.addEventListener(name + 'moved', function (evt) { + assert.notOk(evt.detail); + }); + // emit axismove with no changes + evt = new CustomEvent('axismove', {'detail': {axis: [0.1, 0.2], changed: [false, false]}}); + this.el.dispatchEvent(evt); + // finish next tick + setTimeout(function () { done(); }, 0); + }); + }); + + suite('buttonchanged', function () { + var name = 'trigger'; + var id = 1; + test('if we get buttonchanged, emit ' + name + 'changed', function (done) { + var el = this.el; + var controllerComponent = el.components[controllerComponentName]; + var evt; + // mock isControllerPresent to return true + controllerComponent.isControllerPresentMockValue = true; + // do the check + controllerComponent.checkIfControllerPresent(); + // install event handler listening for triggerchanged + this.el.addEventListener(name + 'changed', function (evt) { + assert.ok(evt.detail); + done(); + }); + // emit buttonchanged + evt = new CustomEvent('buttonchanged', {'detail': {id: id, state: {value: 0.5, pressed: true, touched: true}}}); + this.el.dispatchEvent(evt); + }); + }); + + suite('gamepadconnected / disconnected', function () { + // Due to an apparent bug in FF Nightly + // where only one gamepadconnected / disconnected event is fired, + // which makes it difficult to handle in individual controller entities, + // we no longer remove the controllersupdate listener as a result. + test('if we get gamepadconnected or gamepaddisconnected, check if present', function () { var el = this.el; var controllerComponent = el.components[controllerComponentName]; - var removeControllersUpdateListenerSpy = this.sinon.spy(controllerComponent, 'removeControllersUpdateListener'); var checkIfControllerPresentSpy = this.sinon.spy(controllerComponent, 'checkIfControllerPresent'); + // Because checkIfControllerPresent may be used in bound form, bind and reinstall. + controllerComponent.checkIfControllerPresent = controllerComponent.checkIfControllerPresent.bind(controllerComponent); + controllerComponent.pause(); + controllerComponent.play(); + // mock isControllerPresent to return true + controllerComponent.isControllerPresentMockValue = true; // reset everGotGamepadEvent so we don't think we've looked before delete controllerComponent.everGotGamepadEvent; - // do the call - controllerComponent.onGamepadConnected(); + // fire emulated gamepadconnected event + window.dispatchEvent(new Event('gamepadconnected')); // check assertions - assert.ok(removeControllersUpdateListenerSpy.called); assert.ok(checkIfControllerPresentSpy.called); assert.ok(controllerComponent.everGotGamepadEvent); + // mock isControllerPresent to return false + controllerComponent.isControllerPresentMockValue = false; // reset everGotGamepadEvent so we don't think we've looked before delete controllerComponent.everGotGamepadEvent; - // do the call - controllerComponent.onGamepadDisconnected(); + // fire emulated gamepaddisconnected event + window.dispatchEvent(new Event('gamepaddisconnected')); // check assertions - assert.ok(removeControllersUpdateListenerSpy.called); assert.ok(checkIfControllerPresentSpy.called); assert.ok(controllerComponent.everGotGamepadEvent); }); diff --git a/tests/components/tracked-controls.test.js b/tests/components/tracked-controls.test.js index 6296219c095..f9a50e53dc8 100644 --- a/tests/components/tracked-controls.test.js +++ b/tests/components/tracked-controls.test.js @@ -228,6 +228,7 @@ suite('tracked-controls', function () { assert.deepEqual(component.axis, [0.5, 0.5, 0.5]); assert.equal(emitSpy.getCalls()[0].args[0], 'axismove'); assert.deepEqual(emitSpy.getCalls()[0].args[1].axis, [0.5, 0.5, 0.5]); + assert.deepEqual(emitSpy.getCalls()[0].args[1].changed, [true, true, true]); }); test('emits axismove if axis changed', function () { @@ -241,6 +242,21 @@ suite('tracked-controls', function () { const emitCall = emitSpy.getCalls()[0]; assert.equal(emitCall.args[0], 'axismove'); assert.deepEqual(emitCall.args[1].axis, [1, 1, 1]); + assert.deepEqual(emitCall.args[1].changed, [true, true, true]); + }); + + test('emits axismove with correct axis changed flags', function () { + controller.axes = [0.5, 0.5, 0.5]; + component.tick(); + assert.deepEqual(component.axis, [0.5, 0.5, 0.5]); + + const emitSpy = this.sinon.spy(el, 'emit'); + controller.axes = [1, 0.5, 0.5]; + component.tick(); + const emitCall = emitSpy.getCalls()[0]; + assert.equal(emitCall.args[0], 'axismove'); + assert.deepEqual(emitCall.args[1].axis, [1, 0.5, 0.5]); + assert.deepEqual(emitCall.args[1].changed, [true, false, false]); }); }); diff --git a/tests/components/vive-controls.test.js b/tests/components/vive-controls.test.js index fc50a126635..232e2ea2220 100644 --- a/tests/components/vive-controls.test.js +++ b/tests/components/vive-controls.test.js @@ -1,4 +1,4 @@ -/* global assert, process, setup, suite, test */ +/* global assert, process, setup, suite, test, CustomEvent, Event */ var entityFactory = require('../helpers').entityFactory; var controllerComponentName = 'vive-controls'; @@ -110,26 +110,99 @@ suite(controllerComponentName, function () { }); }); - suite.skip('onGamepadConnected / Disconnected', function () { - test('if we get onGamepadConnected or onGamepadDisconnected, remove periodic change listener and check if present', function () { + suite('axismove', function () { + var name = 'trackpad'; + test('if we get axismove, emit ' + name + 'moved', function (done) { + var el = this.el; + var controllerComponent = el.components[controllerComponentName]; + var evt; + // mock isControllerPresent to return true + controllerComponent.isControllerPresentMockValue = true; + // do the check + controllerComponent.checkIfControllerPresent(); + // install event handler listening for thumbstickmoved + this.el.addEventListener(name + 'moved', function (evt) { + assert.equal(evt.detail.x, 0.1); + assert.equal(evt.detail.y, 0.2); + assert.ok(evt.detail); + done(); + }); + // emit axismove + evt = new CustomEvent('axismove', {'detail': {axis: [0.1, 0.2], changed: [true, false]}}); + this.el.dispatchEvent(evt); + }); + + test('if we get axismove with no changes, do not emit ' + name + 'moved', function (done) { + var el = this.el; + var controllerComponent = el.components[controllerComponentName]; + var evt; + // mock isControllerPresent to return true + controllerComponent.isControllerPresentMockValue = true; + // do the check + controllerComponent.checkIfControllerPresent(); + // install event handler listening for thumbstickmoved + this.el.addEventListener(name + 'moved', function (evt) { + assert.notOk(evt.detail); + }); + // emit axismove + evt = new CustomEvent('axismove', {'detail': {axis: [0.1, 0.2], changed: [false, false]}}); + this.el.dispatchEvent(evt); + // finish next tick + setTimeout(function () { done(); }, 0); + }); + }); + + suite('buttonchanged', function () { + var name = 'trigger'; + var id = 1; + test('if we get buttonchanged, emit ' + name + 'changed', function (done) { + var el = this.el; + var controllerComponent = el.components[controllerComponentName]; + var evt; + // mock isControllerPresent to return true + controllerComponent.isControllerPresentMockValue = true; + // do the check + controllerComponent.checkIfControllerPresent(); + // install event handler listening for triggerchanged + this.el.addEventListener(name + 'changed', function (evt) { + assert.ok(evt.detail); + done(); + }); + // emit buttonchanged + evt = new CustomEvent('buttonchanged', {'detail': {id: id, state: {value: 0.5, pressed: true, touched: true}}}); + this.el.dispatchEvent(evt); + }); + }); + + suite('gamepadconnected / disconnected', function () { + // Due to an apparent bug in FF Nightly + // where only one gamepadconnected / disconnected event is fired, + // which makes it difficult to handle in individual controller entities, + // we no longer remove the controllersupdate listener as a result. + test('if we get gamepadconnected or gamepaddisconnected, check if present', function () { var el = this.el; var controllerComponent = el.components[controllerComponentName]; - var removeControllersUpdateListenerSpy = this.sinon.spy(controllerComponent, 'removeControllersUpdateListener'); var checkIfControllerPresentSpy = this.sinon.spy(controllerComponent, 'checkIfControllerPresent'); + // Because checkIfControllerPresent may be used in bound form, bind and reinstall. + controllerComponent.checkIfControllerPresent = controllerComponent.checkIfControllerPresent.bind(controllerComponent); + controllerComponent.pause(); + controllerComponent.play(); + // mock isControllerPresent to return true + controllerComponent.isControllerPresentMockValue = true; // reset everGotGamepadEvent so we don't think we've looked before delete controllerComponent.everGotGamepadEvent; - // do the call - controllerComponent.onGamepadConnected(); + // fire emulated gamepadconnected event + window.dispatchEvent(new Event('gamepadconnected')); // check assertions - assert.ok(removeControllersUpdateListenerSpy.called); assert.ok(checkIfControllerPresentSpy.called); assert.ok(controllerComponent.everGotGamepadEvent); + // mock isControllerPresent to return false + controllerComponent.isControllerPresentMockValue = false; // reset everGotGamepadEvent so we don't think we've looked before delete controllerComponent.everGotGamepadEvent; - // do the call - controllerComponent.onGamepadDisconnected(); + // fire emulated gamepaddisconnected event + window.dispatchEvent(new Event('gamepaddisconnected')); // check assertions - assert.ok(removeControllersUpdateListenerSpy.called); assert.ok(checkIfControllerPresentSpy.called); assert.ok(controllerComponent.everGotGamepadEvent); }); From 24d1e72d1fdaefbc67608b3340aade6d3c5f5570 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Fri, 24 Mar 2017 16:27:55 -0400 Subject: [PATCH 02/20] make buttons in mapping an array --- src/components/oculus-touch-controls.js | 18 ++++-------------- src/components/vive-controls.js | 10 +++------- 2 files changed, 7 insertions(+), 21 deletions(-) diff --git a/src/components/oculus-touch-controls.js b/src/components/oculus-touch-controls.js index 811d3634f55..37f461a47cf 100644 --- a/src/components/oculus-touch-controls.js +++ b/src/components/oculus-touch-controls.js @@ -43,21 +43,11 @@ module.exports.Component = registerComponent('oculus-touch-controls', { mapping: { 'left': { axes: {'thumbstick': [0, 1]}, - button0: 'thumbstick', - button1: 'trigger', - button2: 'grip', - button3: 'xbutton', - button4: 'ybutton', - button5: 'surface' + buttons: ['thumbstick', 'trigger', 'grip', 'xbutton', 'ybutton', 'surface'] }, 'right': { axes: {'thumbstick': [0, 1]}, - button0: 'thumbstick', - button1: 'trigger', - button2: 'grip', - button3: 'abutton', - button4: 'bbutton', - button5: 'surface' + buttons: ['thumbstick', 'trigger', 'grip', 'abutton', 'abutton', 'surface'] } }, @@ -206,7 +196,7 @@ module.exports.Component = registerComponent('oculus-touch-controls', { }, onButtonChanged: function (evt) { - var button = this.mapping[this.data.hand]['button' + evt.detail.id]; + var button = this.mapping[this.data.hand].buttons[evt.detail.id]; var buttonMeshes = this.buttonMeshes; var isPreviousValueEmulatedTouch; var analogValue; @@ -262,7 +252,7 @@ module.exports.Component = registerComponent('oculus-touch-controls', { }, onButtonEvent: function (id, evtName) { - var buttonName = this.mapping[this.data.hand]['button' + id]; + var buttonName = this.mapping[this.data.hand].buttons[id]; var i; if (Array.isArray(buttonName)) { for (i = 0; i < buttonName.length; i++) { diff --git a/src/components/vive-controls.js b/src/components/vive-controls.js index aade199675e..045097b14df 100644 --- a/src/components/vive-controls.js +++ b/src/components/vive-controls.js @@ -30,11 +30,7 @@ module.exports.Component = registerComponent('vive-controls', { // 4 - system ( never dispatched on this layer ) mapping: { axes: {'trackpad': [0, 1]}, - button0: 'trackpad', - button1: 'trigger', - button2: 'grip', - button3: 'menu', - button4: 'system' + buttons: ['trackpad', 'trigger', 'grip', 'menu', 'system'] }, // Use these labels for detail on axis events such as thumbstickmoved. @@ -155,7 +151,7 @@ module.exports.Component = registerComponent('vive-controls', { }, onButtonChanged: function (evt) { - var button = this.mapping['button' + evt.detail.id]; + var button = this.mapping.buttons[evt.detail.id]; var buttonMeshes = this.buttonMeshes; var value; if (!button) { return; } @@ -207,7 +203,7 @@ module.exports.Component = registerComponent('vive-controls', { }, onButtonEvent: function (id, evtName) { - var buttonName = this.mapping['button' + id]; + var buttonName = this.mapping.buttons[id]; var i; if (Array.isArray(buttonName)) { for (i = 0; i < buttonName.length; i++) { From 03b9645be1c955ebffe8195cd19ff8d81e59bc0c Mon Sep 17 00:00:00 2001 From: machenmusik Date: Mon, 27 Mar 2017 11:42:17 -0400 Subject: [PATCH 03/20] one line, per discussion on PR --- src/components/oculus-touch-controls.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/oculus-touch-controls.js b/src/components/oculus-touch-controls.js index 37f461a47cf..0b73c5d7b8c 100644 --- a/src/components/oculus-touch-controls.js +++ b/src/components/oculus-touch-controls.js @@ -105,9 +105,8 @@ module.exports.Component = registerComponent('oculus-touch-controls', { checkIfControllerPresent: function () { var data = this.data; var isPresent = false; - var whichControllers; // Find which controller matches both prefix and hand. - whichControllers = (this.getGamepadsByPrefix(GAMEPAD_ID_PREFIX) || []) + var whichControllers = (this.getGamepadsByPrefix(GAMEPAD_ID_PREFIX) || []) .filter(function (gamepad) { return gamepad.hand === data.hand; }); if (whichControllers && whichControllers.length) { isPresent = true; } if (isPresent === this.controllerPresent) { return; } From fa0998391d80e99901175c42e62a53c6fd3fb98e Mon Sep 17 00:00:00 2001 From: machenmusik Date: Mon, 27 Mar 2017 23:46:54 -0400 Subject: [PATCH 04/20] fix typo, abutton -> bbutton --- src/components/oculus-touch-controls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/oculus-touch-controls.js b/src/components/oculus-touch-controls.js index 0b73c5d7b8c..4957528358e 100644 --- a/src/components/oculus-touch-controls.js +++ b/src/components/oculus-touch-controls.js @@ -47,7 +47,7 @@ module.exports.Component = registerComponent('oculus-touch-controls', { }, 'right': { axes: {'thumbstick': [0, 1]}, - buttons: ['thumbstick', 'trigger', 'grip', 'abutton', 'abutton', 'surface'] + buttons: ['thumbstick', 'trigger', 'grip', 'abutton', 'bbutton', 'surface'] } }, From 60bbf9550b517605d7bd133f71cf2298c22eb89c Mon Sep 17 00:00:00 2001 From: machenmusik Date: Fri, 7 Apr 2017 15:04:41 -0400 Subject: [PATCH 05/20] fix spurious lines from rebase --- src/components/oculus-touch-controls.js | 2 -- src/components/vive-controls.js | 2 -- tests/components/oculus-touch-controls.test.js | 1 - 3 files changed, 5 deletions(-) diff --git a/src/components/oculus-touch-controls.js b/src/components/oculus-touch-controls.js index 4957528358e..da3a525213e 100644 --- a/src/components/oculus-touch-controls.js +++ b/src/components/oculus-touch-controls.js @@ -136,8 +136,6 @@ module.exports.Component = registerComponent('oculus-touch-controls', { }, pause: function () { - window.removeEventListener('gamepadconnected', this.onGamepadConnected, false); - window.removeEventListener('gamepaddisconnected', this.onGamepadDisconnected, false); this.removeControllersUpdateListener(); this.removeEventListeners(); this.removeControllersUpdateListener(); diff --git a/src/components/vive-controls.js b/src/components/vive-controls.js index 045097b14df..7d83c4238e1 100644 --- a/src/components/vive-controls.js +++ b/src/components/vive-controls.js @@ -42,8 +42,6 @@ module.exports.Component = registerComponent('vive-controls', { this.onControllersUpdate = bind(this.onControllersUpdate, this); this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); - this.onGamepadConnected = bind(this.onGamepadConnected, this); - this.onGamepadDisconnected = bind(this.onGamepadDisconnected, this); this.onAxisMoved = bind(this.onAxisMoved, this); this.onGamepadConnectionEvent = bind(this.onGamepadConnectionEvent, this); }, diff --git a/tests/components/oculus-touch-controls.test.js b/tests/components/oculus-touch-controls.test.js index 0cb662d26dd..5f6c94385b0 100644 --- a/tests/components/oculus-touch-controls.test.js +++ b/tests/components/oculus-touch-controls.test.js @@ -109,7 +109,6 @@ suite(controllerComponentName, function () { // check assertions assert.notOk(injectTrackedControlsSpy.called); assert.notOk(addEventListenersSpy.called); - assert.ok(removeEventListenersSpy.called); assert.notOk(controllerComponent.controllerPresent); }); }); From 1c97bd53e3960870f9c3a4ff8c2ea947d663f6d2 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Fri, 7 Apr 2017 16:44:23 -0400 Subject: [PATCH 06/20] make daydream-controls use new axis/button pattern --- src/components/daydream-controls.js | 67 ++++++++++++++++------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/src/components/daydream-controls.js b/src/components/daydream-controls.js index 713f9862070..e8b8c8a28cd 100644 --- a/src/components/daydream-controls.js +++ b/src/components/daydream-controls.js @@ -29,20 +29,21 @@ module.exports.Component = registerComponent('daydream-controls', { // 1 - menu ( never dispatched on this layer ) // 2 - system ( never dispatched on this layer ) mapping: { - axis0: 'trackpad', - axis1: 'trackpad', - button0: 'trackpad', - button1: 'menu', - button2: 'system' + axes: {'trackpad': [0, 1]}, + buttons: ['trackpad', 'menu', 'system'] }, + // Use these labels for detail on axis events such as thumbstickmoved. + // e.g. for thumbstickmoved detail, the first axis returned is labeled x, and the second is labeled y. + axisLabels: ['x', 'y', 'z', 'w'], + bindMethods: function () { this.onModelLoaded = bind(this.onModelLoaded, this); this.onControllersUpdate = bind(this.onControllersUpdate, this); this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); - this.onGamepadConnected = bind(this.onGamepadConnected, this); - this.onGamepadDisconnected = bind(this.onGamepadDisconnected, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + this.onGamepadConnectionEvent = bind(this.onGamepadConnectionEvent, this); }, init: function () { @@ -87,40 +88,33 @@ module.exports.Component = registerComponent('daydream-controls', { if (isPresent) { this.injectTrackedControls(); } // inject track-controls }, - onGamepadConnected: function (evt) { - // for now, don't disable controller update listening, due to - // apparent issue with FF Nightly only sending one event and seeing one controller; - // this.everGotGamepadEvent = true; - // this.removeControllersUpdateListener(); - this.checkIfControllerPresent(); - }, - - onGamepadDisconnected: function (evt) { - // for now, don't disable controller update listening, due to - // apparent issue with FF Nightly only sending one event and seeing one controller; - // this.everGotGamepadEvent = true; - // this.removeControllersUpdateListener(); + onGamepadConnectionEvent: function (evt) { + this.everGotGamepadEvent = true; + // Due to an apparent bug in FF Nightly + // where only one gamepadconnected / disconnected event is fired, + // which makes it difficult to handle in individual controller entities, + // we no longer remove the controllersupdate listener as a result. this.checkIfControllerPresent(); }, play: function () { this.checkIfControllerPresent(); - window.addEventListener('gamepadconnected', this.onGamepadConnected, false); - window.addEventListener('gamepaddisconnected', this.onGamepadDisconnected, false); this.addControllersUpdateListener(); - this.addEventListeners(); + window.addEventListener('gamepadconnected', this.onGamepadConnectionEvent, false); + window.addEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false); }, pause: function () { - window.removeEventListener('gamepadconnected', this.onGamepadConnected, false); - window.removeEventListener('gamepaddisconnected', this.onGamepadDisconnected, false); - this.removeControllersUpdateListener(); this.removeEventListeners(); + this.removeControllersUpdateListener(); + window.removeEventListener('gamepadconnected', this.onGamepadConnectionEvent, false); + window.removeEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false); }, injectTrackedControls: function () { var el = this.el; var data = this.data; + this.addEventListeners(); el.setAttribute('tracked-controls', {idPrefix: GAMEPAD_ID_PREFIX, hand: data.hand, rotationOffset: data.rotationOffset}); if (!this.data.model) { return; } this.el.setAttribute('obj-model', { @@ -156,12 +150,27 @@ module.exports.Component = registerComponent('daydream-controls', { }, onAxisMoved: function (evt) { - if (evt.detail.axis[0] === 0 && evt.detail.axis[1] === 0) { return; } - this.el.emit('trackpadmoved', { x: evt.detail.axis[0], y: evt.detail.axis[1] }); + var self = this; + var axesMapping = this.mapping.axes; + + // In theory, it might be better to use mapping from axis to control. + // In practice, it is not clear whether the additional overhead is worthwhile, + // and if we did grouping of axes, we really need de-duplication there. + Object.keys(axesMapping).forEach(function (key) { + var value = axesMapping[key]; + var detail = {}; + var changed = !evt.detail.changed; + if (!changed) { value.forEach(function (axisNumber) { changed |= evt.detail.changed[axisNumber]; }); } + if (changed) { + value.forEach(function (axisNumber) { detail[self.axisLabels[axisNumber]] = evt.detail.axis[axisNumber]; }); + self.el.emit(key + 'moved', detail); + // If we updated the model based on axis values, that call would go here. + } + }); }, onButtonEvent: function (id, evtName) { - var buttonName = this.mapping['button' + id]; + var buttonName = this.mapping.buttons[id]; var i; if (Array.isArray(buttonName)) { for (i = 0; i < buttonName.length; i++) { From 0f2cb4f0c306e011b95b823b2f3f8497161a33ce Mon Sep 17 00:00:00 2001 From: machenmusik Date: Wed, 12 Apr 2017 15:29:44 -0400 Subject: [PATCH 07/20] given gamepadconnected behavior, don't use it === if we got gamepad connect / disconnect events, don't check if controller present on every controllersupdate still seeing issues with gamepadconnected/disconnected propagation given gamepadconnected behavior, don't use it --- src/components/oculus-touch-controls.js | 24 ++++--------------- src/components/vive-controls.js | 23 ++++-------------- .../components/oculus-touch-controls.test.js | 14 ++--------- tests/components/vive-controls.test.js | 14 ++--------- 4 files changed, 14 insertions(+), 61 deletions(-) diff --git a/src/components/oculus-touch-controls.js b/src/components/oculus-touch-controls.js index da3a525213e..6a5116e1b89 100644 --- a/src/components/oculus-touch-controls.js +++ b/src/components/oculus-touch-controls.js @@ -60,7 +60,6 @@ module.exports.Component = registerComponent('oculus-touch-controls', { this.onControllersUpdate = bind(this.onControllersUpdate, this); this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); this.onAxisMoved = bind(this.onAxisMoved, this); - this.onGamepadConnectionEvent = bind(this.onGamepadConnectionEvent, this); }, init: function () { @@ -72,7 +71,6 @@ module.exports.Component = registerComponent('oculus-touch-controls', { this.onButtonTouchStart = function (evt) { self.onButtonEvent(evt.detail.id, 'touchstart'); }; this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt.detail.id, 'touchend'); }; this.controllerPresent = false; - this.everGotGamepadEvent = false; this.lastControllerCheck = 0; this.previousButtonValues = {}; this.bindMethods(); @@ -119,28 +117,19 @@ module.exports.Component = registerComponent('oculus-touch-controls', { } else { this.removeEventListeners(); } }, - onGamepadConnectionEvent: function (evt) { - this.everGotGamepadEvent = true; - // Due to an apparent bug in FF Nightly - // where only one gamepadconnected / disconnected event is fired, - // which makes it difficult to handle in individual controller entities, - // we no longer remove the controllersupdate listener as a result. - this.checkIfControllerPresent(); - }, - play: function () { this.checkIfControllerPresent(); this.addControllersUpdateListener(); - window.addEventListener('gamepadconnected', this.onGamepadConnectionEvent, false); - window.addEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false); + // Note that due to gamepadconnected event propagation issues, we don't rely on events. + window.addEventListener('gamepaddisconnected', this.checkIfControllerPresent, false); }, pause: function () { this.removeControllersUpdateListener(); this.removeEventListeners(); this.removeControllersUpdateListener(); - window.removeEventListener('gamepadconnected', this.onGamepadConnectionEvent, false); - window.removeEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false); + // Note that due to gamepadconnected event propagation issues, we don't rely on events. + window.removeEventListener('gamepaddisconnected', this.checkIfControllerPresent, false); }, updateControllerModel: function () { @@ -179,10 +168,7 @@ module.exports.Component = registerComponent('oculus-touch-controls', { }, onControllersUpdate: function () { - // Due to an apparent bug in FF Nightly - // where only one gamepadconnected / disconnected event is fired, - // which makes it difficult to handle in individual controller entities, - // we no longer remove the controllersupdate listener when we get a gamepad event. + // Note that due to gamepadconnected event propagation issues, we don't rely on events. this.checkIfControllerPresent(); }, diff --git a/src/components/vive-controls.js b/src/components/vive-controls.js index 7d83c4238e1..971cc9ed02a 100644 --- a/src/components/vive-controls.js +++ b/src/components/vive-controls.js @@ -43,7 +43,6 @@ module.exports.Component = registerComponent('vive-controls', { this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); this.onAxisMoved = bind(this.onAxisMoved, this); - this.onGamepadConnectionEvent = bind(this.onGamepadConnectionEvent, this); }, init: function () { @@ -56,7 +55,6 @@ module.exports.Component = registerComponent('vive-controls', { this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt.detail.id, 'touchend'); }; this.onAxisMoved = bind(this.onAxisMoved, this); this.controllerPresent = false; - this.everGotGamepadEvent = false; this.lastControllerCheck = 0; this.bindMethods(); this.isControllerPresent = isControllerPresent; // to allow mock @@ -99,27 +97,18 @@ module.exports.Component = registerComponent('vive-controls', { } else { this.removeEventListeners(); } }, - onGamepadConnectionEvent: function (evt) { - this.everGotGamepadEvent = true; - // Due to an apparent bug in FF Nightly - // where only one gamepadconnected / disconnected event is fired, - // which makes it difficult to handle in individual controller entities, - // we no longer remove the controllersupdate listener as a result. - this.checkIfControllerPresent(); - }, - play: function () { this.checkIfControllerPresent(); this.addControllersUpdateListener(); - window.addEventListener('gamepadconnected', this.onGamepadConnectionEvent, false); - window.addEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false); + // Note that due to gamepadconnected event propagation issues, we don't rely on events. + window.addEventListener('gamepaddisconnected', this.checkIfControllerPresent, false); }, pause: function () { this.removeEventListeners(); this.removeControllersUpdateListener(); - window.removeEventListener('gamepadconnected', this.onGamepadConnectionEvent, false); - window.removeEventListener('gamepaddisconnected', this.onGamepadConnectionEvent, false); + // Note that due to gamepadconnected event propagation issues, we don't rely on events. + window.removeEventListener('gamepaddisconnected', this.checkIfControllerPresent, false); }, injectTrackedControls: function () { @@ -144,9 +133,7 @@ module.exports.Component = registerComponent('vive-controls', { this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); }, - onControllersUpdate: function () { - if (!this.everGotGamepadEvent) { this.checkIfControllerPresent(); } - }, + onControllersUpdate: function () { this.checkIfControllerPresent(); }, onButtonChanged: function (evt) { var button = this.mapping.buttons[evt.detail.id]; diff --git a/tests/components/oculus-touch-controls.test.js b/tests/components/oculus-touch-controls.test.js index 5f6c94385b0..84adfbfc100 100644 --- a/tests/components/oculus-touch-controls.test.js +++ b/tests/components/oculus-touch-controls.test.js @@ -177,12 +177,12 @@ suite(controllerComponentName, function () { }); }); - suite('gamepadconnected / disconnected', function () { + suite('gamepaddisconnected', function () { // Due to an apparent bug in FF Nightly // where only one gamepadconnected / disconnected event is fired, // which makes it difficult to handle in individual controller entities, // we no longer remove the controllersupdate listener as a result. - test('if we get gamepadconnected or gamepaddisconnected, check if present', function () { + test('if we get gamepaddisconnected, check if present', function () { var el = this.el; var controllerComponent = el.components[controllerComponentName]; var checkIfControllerPresentSpy = this.sinon.spy(controllerComponent, 'checkIfControllerPresent'); @@ -190,15 +190,6 @@ suite(controllerComponentName, function () { controllerComponent.checkIfControllerPresent = controllerComponent.checkIfControllerPresent.bind(controllerComponent); controllerComponent.pause(); controllerComponent.play(); - // mock isControllerPresent to return true - controllerComponent.isControllerPresentMockValue = true; - // reset everGotGamepadEvent so we don't think we've looked before - delete controllerComponent.everGotGamepadEvent; - // fire emulated gamepadconnected event - window.dispatchEvent(new Event('gamepadconnected')); - // check assertions - assert.ok(checkIfControllerPresentSpy.called); - assert.ok(controllerComponent.everGotGamepadEvent); // mock isControllerPresent to return false controllerComponent.isControllerPresentMockValue = false; // reset everGotGamepadEvent so we don't think we've looked before @@ -207,7 +198,6 @@ suite(controllerComponentName, function () { window.dispatchEvent(new Event('gamepaddisconnected')); // check assertions assert.ok(checkIfControllerPresentSpy.called); - assert.ok(controllerComponent.everGotGamepadEvent); }); }); }); diff --git a/tests/components/vive-controls.test.js b/tests/components/vive-controls.test.js index 232e2ea2220..dcf02d6bedc 100644 --- a/tests/components/vive-controls.test.js +++ b/tests/components/vive-controls.test.js @@ -174,12 +174,12 @@ suite(controllerComponentName, function () { }); }); - suite('gamepadconnected / disconnected', function () { + suite('gamepaddisconnected', function () { // Due to an apparent bug in FF Nightly // where only one gamepadconnected / disconnected event is fired, // which makes it difficult to handle in individual controller entities, // we no longer remove the controllersupdate listener as a result. - test('if we get gamepadconnected or gamepaddisconnected, check if present', function () { + test('if we get gamepaddisconnected, check if present', function () { var el = this.el; var controllerComponent = el.components[controllerComponentName]; var checkIfControllerPresentSpy = this.sinon.spy(controllerComponent, 'checkIfControllerPresent'); @@ -187,15 +187,6 @@ suite(controllerComponentName, function () { controllerComponent.checkIfControllerPresent = controllerComponent.checkIfControllerPresent.bind(controllerComponent); controllerComponent.pause(); controllerComponent.play(); - // mock isControllerPresent to return true - controllerComponent.isControllerPresentMockValue = true; - // reset everGotGamepadEvent so we don't think we've looked before - delete controllerComponent.everGotGamepadEvent; - // fire emulated gamepadconnected event - window.dispatchEvent(new Event('gamepadconnected')); - // check assertions - assert.ok(checkIfControllerPresentSpy.called); - assert.ok(controllerComponent.everGotGamepadEvent); // mock isControllerPresent to return false controllerComponent.isControllerPresentMockValue = false; // reset everGotGamepadEvent so we don't think we've looked before @@ -204,7 +195,6 @@ suite(controllerComponentName, function () { window.dispatchEvent(new Event('gamepaddisconnected')); // check assertions assert.ok(checkIfControllerPresentSpy.called); - assert.ok(controllerComponent.everGotGamepadEvent); }); }); }); From 6a030d9dfcadac13e55ce7fc27af84f406723e76 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Wed, 12 Apr 2017 22:48:08 -0400 Subject: [PATCH 08/20] isOculusTouch no longer a method to test; update tests for removed animation mapping --- tests/components/hand-controls.test.js | 42 +++++--------------------- 1 file changed, 8 insertions(+), 34 deletions(-) diff --git a/tests/components/hand-controls.test.js b/tests/components/hand-controls.test.js index f9c8cc8fa96..3c3fa423f6c 100644 --- a/tests/components/hand-controls.test.js +++ b/tests/components/hand-controls.test.js @@ -21,32 +21,6 @@ suite(controllerComponentName, function () { }); }); - suite('isOculusTouch', function () { - test('true if controller id starts with "Oculus Touch"', function () { - var el = this.el; - var controllerComponent = el.components[controllerComponentName]; - var trackedControls; - el.setAttribute('tracked-controls', ''); - trackedControls = el.components['tracked-controls']; - // mock controller - trackedControls.controller = {id: 'Oculus Touch (Left)', connected: true}; - // do the check - assert.ok(controllerComponent.isOculusTouchController()); - }); - - test('false if controller id does not start with "Oculus Touch"', function () { - var el = this.el; - var controllerComponent = el.components[controllerComponentName]; - var trackedControls; - el.setAttribute('tracked-controls', ''); - trackedControls = el.components['tracked-controls']; - // mock controller - trackedControls.controller = {id: 'OpenVR Gamepad', connected: true}; - // do the check - assert.notOk(controllerComponent.isOculusTouchController()); - }); - }); - suite('determineGesture', function () { test('if nothing touched or triggered, no gesture', function () { var el = this.el; @@ -64,7 +38,7 @@ suite(controllerComponentName, function () { assert.notOk(controllerComponent.determineGesture()); }); - test('if non-Oculus Touch and only trackpad. pointing gesture', function () { + test('if non-Oculus Touch and only trackpad. Point gesture', function () { var el = this.el; var controllerComponent = el.components[controllerComponentName]; var trackedControls; @@ -82,10 +56,10 @@ suite(controllerComponentName, function () { controllerComponent.pressedButtons['BorY'] = false; controllerComponent.pressedButtons['surface'] = false; // do the check - assert.equal(controllerComponent.determineGesture(), 'pointing'); + assert.equal(controllerComponent.determineGesture(), 'Point'); }); - test('if non-Oculus Touch and grip or trigger, gesture = fist', function () { + test('if non-Oculus Touch and grip or trigger, gesture = Fist', function () { var el = this.el; var controllerComponent = el.components[controllerComponentName]; var trackedControls; @@ -103,25 +77,25 @@ suite(controllerComponentName, function () { controllerComponent.pressedButtons['BorY'] = false; controllerComponent.pressedButtons['surface'] = false; // do the check - assert.equal(controllerComponent.determineGesture(), 'fist'); + assert.equal(controllerComponent.determineGesture(), 'Fist'); // mock button / touch flags controllerComponent.pressedButtons['grip'] = false; controllerComponent.pressedButtons['trigger'] = true; // do the check - assert.equal(controllerComponent.determineGesture(), 'fist'); + assert.equal(controllerComponent.determineGesture(), 'Fist'); // mock button / touch flags controllerComponent.pressedButtons['grip'] = true; controllerComponent.pressedButtons['trigger'] = true; // do the check - assert.equal(controllerComponent.determineGesture(), 'fist'); + assert.equal(controllerComponent.determineGesture(), 'Fist'); // mock button / touch flags controllerComponent.pressedButtons['trackpad'] = true; // do the check - assert.equal(controllerComponent.determineGesture(), 'fist'); + assert.equal(controllerComponent.determineGesture(), 'Fist'); // mock button / touch flags controllerComponent.pressedButtons['menu'] = true; // do the check - assert.equal(controllerComponent.determineGesture(), 'fist'); + assert.equal(controllerComponent.determineGesture(), 'Fist'); }); }); }); From f2043811f55d54cd62bf6ae0d78b1bb2e3a5b87c Mon Sep 17 00:00:00 2001 From: machenmusik Date: Wed, 12 Apr 2017 22:50:18 -0400 Subject: [PATCH 09/20] move isEmulatedTouchEvent to utils; add emulated touch to Vive trigger --- src/components/oculus-touch-controls.js | 15 +++----------- src/components/vive-controls.js | 26 ++++++++++++++++++++----- src/utils/tracked-controls.js | 8 ++++++++ 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/components/oculus-touch-controls.js b/src/components/oculus-touch-controls.js index 6a5116e1b89..78fc41c3337 100644 --- a/src/components/oculus-touch-controls.js +++ b/src/components/oculus-touch-controls.js @@ -2,6 +2,7 @@ var registerComponent = require('../core/component').registerComponent; var bind = require('../utils/bind'); var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; var getGamepadsByPrefix = require('../utils/tracked-controls').getGamepadsByPrefix; +var isEmulatedTouchEvent = require('../utils/tracked-controls').isEmulatedTouchEvent; var TOUCH_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/oculus/oculus-touch-controller-'; var TOUCH_CONTROLLER_MODEL_OBJ_URL_L = TOUCH_CONTROLLER_MODEL_BASE_URL + 'left.obj'; @@ -13,10 +14,6 @@ var GAMEPAD_ID_PREFIX = 'Oculus Touch'; var PIVOT_OFFSET = {x: 0, y: -0.015, z: 0.04}; -// currently, browser bugs prevent capacitive touch events from firing on trigger and grip; -// however those have analog values, and this (below button-down values) can be used to fake them -var EMULATED_TOUCH_THRESHOLD = 0.001; - /** * Oculus Touch Controls Component * Interfaces with Oculus Touch controllers and maps Gamepad events to @@ -172,12 +169,6 @@ module.exports.Component = registerComponent('oculus-touch-controls', { this.checkIfControllerPresent(); }, - // Currently, browser bugs prevent capacitive touch events from firing on trigger and grip; - // however those have analog values, and this (below button-down values) can be used to fake them. - isEmulatedTouchEvent: function (analogValue) { - return analogValue && (analogValue >= EMULATED_TOUCH_THRESHOLD); - }, - onButtonChanged: function (evt) { var button = this.mapping[this.data.hand].buttons[evt.detail.id]; var buttonMeshes = this.buttonMeshes; @@ -192,9 +183,9 @@ module.exports.Component = registerComponent('oculus-touch-controls', { // touch events aren't happening (touched is stuck true); // synthesize touch events from very low analog values. analogValue = evt.detail.state.value; - isPreviousValueEmulatedTouch = this.isEmulatedTouchEvent(this.previousButtonValues[button]); + isPreviousValueEmulatedTouch = isEmulatedTouchEvent(this.previousButtonValues[button]); this.previousButtonValues[button] = analogValue; - isEmulatedTouch = this.isEmulatedTouchEvent(analogValue); + isEmulatedTouch = isEmulatedTouchEvent(analogValue); if (isEmulatedTouch !== isPreviousValueEmulatedTouch) { (isEmulatedTouch ? this.onButtonTouchStart : this.onButtonTouchEnd)(evt); } diff --git a/src/components/vive-controls.js b/src/components/vive-controls.js index 971cc9ed02a..62c041df63f 100644 --- a/src/components/vive-controls.js +++ b/src/components/vive-controls.js @@ -1,6 +1,7 @@ var registerComponent = require('../core/component').registerComponent; var bind = require('../utils/bind'); var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; +var isEmulatedTouchEvent = require('../utils/tracked-controls').isEmulatedTouchEvent; var VIVE_CONTROLLER_MODEL_OBJ_URL = 'https://cdn.aframe.io/controllers/vive/vr_controller_vive.obj'; var VIVE_CONTROLLER_MODEL_OBJ_MTL = 'https://cdn.aframe.io/controllers/vive/vr_controller_vive.mtl'; @@ -56,6 +57,7 @@ module.exports.Component = registerComponent('vive-controls', { this.onAxisMoved = bind(this.onAxisMoved, this); this.controllerPresent = false; this.lastControllerCheck = 0; + this.previousButtonValues = {}; this.bindMethods(); this.isControllerPresent = isControllerPresent; // to allow mock }, @@ -138,13 +140,27 @@ module.exports.Component = registerComponent('vive-controls', { onButtonChanged: function (evt) { var button = this.mapping.buttons[evt.detail.id]; var buttonMeshes = this.buttonMeshes; - var value; + var analogValue; + var isEmulatedTouch; + var isPreviousValueEmulatedTouch; if (!button) { return; } - // Update button mesh, if any. - if (buttonMeshes && button === 'trigger') { - value = evt.detail.state.value; - buttonMeshes.trigger.rotation.x = -value * (Math.PI / 12); + if (button === 'trigger') { + // At the moment, if trigger, + // touch events aren't happening; + // synthesize touch events from very low analog values. + analogValue = evt.detail.state.value; + isPreviousValueEmulatedTouch = isEmulatedTouchEvent(this.previousButtonValues[button]); + this.previousButtonValues[button] = analogValue; + isEmulatedTouch = isEmulatedTouchEvent(analogValue); + if (isEmulatedTouch !== isPreviousValueEmulatedTouch) { + (isEmulatedTouch ? this.onButtonTouchStart : this.onButtonTouchEnd)(evt); + } + + // Update button mesh, if any. + if (buttonMeshes && buttonMeshes.trigger) { + buttonMeshes.trigger.rotation.x = -analogValue * (Math.PI / 12); + } } // Pass along changed event with button state, using button mapping for convenience. diff --git a/src/utils/tracked-controls.js b/src/utils/tracked-controls.js index 7a59ef63e7e..7bd6db3c1b6 100644 --- a/src/utils/tracked-controls.js +++ b/src/utils/tracked-controls.js @@ -1,4 +1,7 @@ var DEFAULT_HANDEDNESS = require('../constants').DEFAULT_HANDEDNESS; +// Currently, browser bugs prevent capacitive touch events from firing on trigger and grip; +// however those have analog values, and this (below button-down values) can be used to fake them. +var EMULATED_TOUCH_THRESHOLD = 0.001; /** * Return enumerated gamepads matching id prefix. @@ -61,3 +64,8 @@ module.exports.isControllerPresent = function (sceneEl, idPrefix, queryObject) { return isPresent; }; +module.exports.isEmulatedTouchEvent = function (analogValue) { + // Currently, browser bugs prevent capacitive touch events from firing on trigger and grip; + // however those have analog values, and this (below button-down values) can be used to fake them. + return analogValue && (analogValue >= EMULATED_TOUCH_THRESHOLD); +}; From c48fe0d57d7beca3ae6bed06a89b331b82172d90 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Thu, 13 Apr 2017 21:20:43 -0400 Subject: [PATCH 10/20] Nightly will be sending touched, so don't emulate touches anymore --- src/components/oculus-touch-controls.js | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/components/oculus-touch-controls.js b/src/components/oculus-touch-controls.js index 78fc41c3337..07f8f070a58 100644 --- a/src/components/oculus-touch-controls.js +++ b/src/components/oculus-touch-controls.js @@ -2,7 +2,6 @@ var registerComponent = require('../core/component').registerComponent; var bind = require('../utils/bind'); var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; var getGamepadsByPrefix = require('../utils/tracked-controls').getGamepadsByPrefix; -var isEmulatedTouchEvent = require('../utils/tracked-controls').isEmulatedTouchEvent; var TOUCH_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/oculus/oculus-touch-controller-'; var TOUCH_CONTROLLER_MODEL_OBJ_URL_L = TOUCH_CONTROLLER_MODEL_BASE_URL + 'left.obj'; @@ -172,24 +171,11 @@ module.exports.Component = registerComponent('oculus-touch-controls', { onButtonChanged: function (evt) { var button = this.mapping[this.data.hand].buttons[evt.detail.id]; var buttonMeshes = this.buttonMeshes; - var isPreviousValueEmulatedTouch; var analogValue; - var isEmulatedTouch; if (!button) { return; } - if (button === 'trigger' || button === 'grip') { - // At the moment, if trigger or grip, - // touch events aren't happening (touched is stuck true); - // synthesize touch events from very low analog values. - analogValue = evt.detail.state.value; - isPreviousValueEmulatedTouch = isEmulatedTouchEvent(this.previousButtonValues[button]); - this.previousButtonValues[button] = analogValue; - isEmulatedTouch = isEmulatedTouchEvent(analogValue); - if (isEmulatedTouch !== isPreviousValueEmulatedTouch) { - (isEmulatedTouch ? this.onButtonTouchStart : this.onButtonTouchEnd)(evt); - } - } + if (button === 'trigger' || button === 'grip') { analogValue = evt.detail.state.value; } // Update trigger and/or grip meshes, if any. if (buttonMeshes) { From 5b8fdc5bb7ad5a749c3b2347b870224c0211fb2c Mon Sep 17 00:00:00 2001 From: machenmusik Date: Thu, 13 Apr 2017 21:22:50 -0400 Subject: [PATCH 11/20] suppress trigger touch events, since we're emulating them --- src/components/vive-controls.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/vive-controls.js b/src/components/vive-controls.js index 62c041df63f..9033173f473 100644 --- a/src/components/vive-controls.js +++ b/src/components/vive-controls.js @@ -52,8 +52,14 @@ module.exports.Component = registerComponent('vive-controls', { this.onButtonChanged = bind(this.onButtonChanged, this); this.onButtonDown = function (evt) { self.onButtonEvent(evt.detail.id, 'down'); }; this.onButtonUp = function (evt) { self.onButtonEvent(evt.detail.id, 'up'); }; - this.onButtonTouchStart = function (evt) { self.onButtonEvent(evt.detail.id, 'touchstart'); }; - this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt.detail.id, 'touchend'); }; + this.onButtonTouchStart = function (evt) { + // we're emulating trigger touch, so suppress real events + if (evt.detail.id !== 'trigger') { self.onButtonEvent(evt.detail.id, 'touchstart'); } + }; + this.onButtonTouchEnd = function (evt) { + // we're emulating trigger touch, so suppress real events + if (evt.detail.id !== 'trigger') { self.onButtonEvent(evt.detail.id, 'touchend'); } + }; this.onAxisMoved = bind(this.onAxisMoved, this); this.controllerPresent = false; this.lastControllerCheck = 0; @@ -154,7 +160,7 @@ module.exports.Component = registerComponent('vive-controls', { this.previousButtonValues[button] = analogValue; isEmulatedTouch = isEmulatedTouchEvent(analogValue); if (isEmulatedTouch !== isPreviousValueEmulatedTouch) { - (isEmulatedTouch ? this.onButtonTouchStart : this.onButtonTouchEnd)(evt); + this.onButtonEvent(evt.detail.id, isEmulatedTouch ? 'touchstart' : 'touchend'); } // Update button mesh, if any. From 8b9380eeb0f2bd24c8b798633dd31dc2c9e64663 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Thu, 13 Apr 2017 21:43:37 -0400 Subject: [PATCH 12/20] remove vive emulated touch per discussion on PR --- src/components/vive-controls.js | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/src/components/vive-controls.js b/src/components/vive-controls.js index 9033173f473..e6c86cc2f1a 100644 --- a/src/components/vive-controls.js +++ b/src/components/vive-controls.js @@ -1,7 +1,6 @@ var registerComponent = require('../core/component').registerComponent; var bind = require('../utils/bind'); var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; -var isEmulatedTouchEvent = require('../utils/tracked-controls').isEmulatedTouchEvent; var VIVE_CONTROLLER_MODEL_OBJ_URL = 'https://cdn.aframe.io/controllers/vive/vr_controller_vive.obj'; var VIVE_CONTROLLER_MODEL_OBJ_MTL = 'https://cdn.aframe.io/controllers/vive/vr_controller_vive.mtl'; @@ -52,14 +51,8 @@ module.exports.Component = registerComponent('vive-controls', { this.onButtonChanged = bind(this.onButtonChanged, this); this.onButtonDown = function (evt) { self.onButtonEvent(evt.detail.id, 'down'); }; this.onButtonUp = function (evt) { self.onButtonEvent(evt.detail.id, 'up'); }; - this.onButtonTouchStart = function (evt) { - // we're emulating trigger touch, so suppress real events - if (evt.detail.id !== 'trigger') { self.onButtonEvent(evt.detail.id, 'touchstart'); } - }; - this.onButtonTouchEnd = function (evt) { - // we're emulating trigger touch, so suppress real events - if (evt.detail.id !== 'trigger') { self.onButtonEvent(evt.detail.id, 'touchend'); } - }; + this.onButtonTouchStart = function (evt) { self.onButtonEvent(evt.detail.id, 'touchstart'); }; + this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt.detail.id, 'touchend'); }; this.onAxisMoved = bind(this.onAxisMoved, this); this.controllerPresent = false; this.lastControllerCheck = 0; @@ -147,22 +140,10 @@ module.exports.Component = registerComponent('vive-controls', { var button = this.mapping.buttons[evt.detail.id]; var buttonMeshes = this.buttonMeshes; var analogValue; - var isEmulatedTouch; - var isPreviousValueEmulatedTouch; if (!button) { return; } if (button === 'trigger') { - // At the moment, if trigger, - // touch events aren't happening; - // synthesize touch events from very low analog values. analogValue = evt.detail.state.value; - isPreviousValueEmulatedTouch = isEmulatedTouchEvent(this.previousButtonValues[button]); - this.previousButtonValues[button] = analogValue; - isEmulatedTouch = isEmulatedTouchEvent(analogValue); - if (isEmulatedTouch !== isPreviousValueEmulatedTouch) { - this.onButtonEvent(evt.detail.id, isEmulatedTouch ? 'touchstart' : 'touchend'); - } - // Update button mesh, if any. if (buttonMeshes && buttonMeshes.trigger) { buttonMeshes.trigger.rotation.x = -analogValue * (Math.PI / 12); From 9bb1b581d3a1c6ab7cff4ec4e69007de837c2943 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Thu, 13 Apr 2017 21:48:56 -0400 Subject: [PATCH 13/20] remove isEmulatedTouchEvent --- src/utils/tracked-controls.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/utils/tracked-controls.js b/src/utils/tracked-controls.js index 7bd6db3c1b6..eba5f6d08be 100644 --- a/src/utils/tracked-controls.js +++ b/src/utils/tracked-controls.js @@ -1,7 +1,4 @@ var DEFAULT_HANDEDNESS = require('../constants').DEFAULT_HANDEDNESS; -// Currently, browser bugs prevent capacitive touch events from firing on trigger and grip; -// however those have analog values, and this (below button-down values) can be used to fake them. -var EMULATED_TOUCH_THRESHOLD = 0.001; /** * Return enumerated gamepads matching id prefix. @@ -63,9 +60,3 @@ module.exports.isControllerPresent = function (sceneEl, idPrefix, queryObject) { } return isPresent; }; - -module.exports.isEmulatedTouchEvent = function (analogValue) { - // Currently, browser bugs prevent capacitive touch events from firing on trigger and grip; - // however those have analog values, and this (below button-down values) can be used to fake them. - return analogValue && (analogValue >= EMULATED_TOUCH_THRESHOLD); -}; From 299d2c1e0db45db2a4ae5dbbe24e12c9f47be996 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Thu, 13 Apr 2017 23:39:26 -0400 Subject: [PATCH 14/20] minor edit per discussion --- src/components/oculus-touch-controls.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/oculus-touch-controls.js b/src/components/oculus-touch-controls.js index 07f8f070a58..0eca41d1af6 100644 --- a/src/components/oculus-touch-controls.js +++ b/src/components/oculus-touch-controls.js @@ -234,7 +234,9 @@ module.exports.Component = registerComponent('oculus-touch-controls', { var value = axesMapping[key]; var detail = {}; var changed = !evt.detail.changed; - if (!changed) { value.forEach(function (axisNumber) { changed |= evt.detail.changed[axisNumber]; }); } + if (!changed) { + value.forEach(function (axisNumber) { changed |= evt.detail.changed[axisNumber]; }); + } if (changed) { value.forEach(function (axisNumber) { detail[self.axisLabels[axisNumber]] = evt.detail.axis[axisNumber]; }); self.el.emit(key + 'moved', detail); From 41a1b555d148ac360511dbbedda2cd16fb52e591 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Fri, 31 Mar 2017 02:27:22 -0400 Subject: [PATCH 15/20] add gearvr-controls --- docs/components/gearvr-controls.md | 48 +++++++ src/components/gearvr-controls.js | 203 +++++++++++++++++++++++++++++ src/components/hand-controls.js | 1 + src/components/index.js | 1 + 4 files changed, 253 insertions(+) create mode 100644 docs/components/gearvr-controls.md create mode 100644 src/components/gearvr-controls.js diff --git a/docs/components/gearvr-controls.md b/docs/components/gearvr-controls.md new file mode 100644 index 00000000000..3b4d50a3f54 --- /dev/null +++ b/docs/components/gearvr-controls.md @@ -0,0 +1,48 @@ +--- +title: gearvr-controls +type: components +layout: docs +parent_section: components +--- + +[trackedcontrols]: ./tracked-controls.md + +The gearvr-controls component interfaces with the Samsung/Oculus Gear VR controllers. +It wraps the [tracked-controls component][trackedcontrols] while adding button +mappings, events, and a Gear VR controller model that highlights the touched +and/or pressed buttons (trackpad, trigger). + +## Example + +```html + + +``` + +## Value + +| Property | Description | Default | +|----------------------|----------------------------------------------------|---------| +| buttonColor | Button colors when not pressed. | #000000 | +| buttonTouchedColor | Button colors when touched. | #777777 | +| buttonHighlightColor | Button colors when pressed and active. | #FFFFFF | +| hand | The hand that will be tracked (i.e., right, left). | right | +| model | Whether the Daydream controller model is loaded. | true | +| rotationOffset | Offset to apply to model rotation. | 0 | + +## Events + +| Event Name | Description | +| ---------- | ----------- | +| trackpaddown | Trackpad pressed. | +| trackpadup | Trackpad released. | +| trackpadtouchstart | Trackpad touched. | +| trackpadtouchend | Trackpad not touched. | +| triggerdown | Trigger pressed. | +| triggerup | Trigger released. | + +## Assets + +- [Controller OBJ](https://cdn.aframe.io/controllers/google/vr_controller_daydream.obj) +- [Controller MTL](https://cdn.aframe.io/controllers/google/vr_controller_daydream.mtl) + diff --git a/src/components/gearvr-controls.js b/src/components/gearvr-controls.js new file mode 100644 index 00000000000..da850b891b2 --- /dev/null +++ b/src/components/gearvr-controls.js @@ -0,0 +1,203 @@ +var registerComponent = require('../core/component').registerComponent; +var bind = require('../utils/bind'); +var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; + +var DEFAULT_HANDEDNESS = require('../constants').DEFAULT_HANDEDNESS; + +var GEARVR_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/google/'; +var GEARVR_CONTROLLER_MODEL_OBJ_URL = GEARVR_CONTROLLER_MODEL_BASE_URL + 'vr_controller_daydream.obj'; +var GEARVR_CONTROLLER_MODEL_OBJ_MTL = GEARVR_CONTROLLER_MODEL_BASE_URL + 'vr_controller_daydream.mtl'; + +var GAMEPAD_ID_PREFIX = 'Gear VR'; + +/** + * Vive Controls Component + * Interfaces with vive controllers and maps Gamepad events to + * common controller buttons: trackpad, trigger, grip, menu and system + * It loads a controller model and highlights the pressed buttons + */ +module.exports.Component = registerComponent('gearvr-controls', { + schema: { + hand: {default: DEFAULT_HANDEDNESS}, // This informs the degenerate arm model. + buttonColor: {type: 'color', default: '#000000'}, + buttonTouchedColor: {type: 'color', default: '#777777'}, + buttonHighlightColor: {type: 'color', default: '#FFFFFF'}, + model: {default: true}, + rotationOffset: {default: 0} // use -999 as sentinel value to auto-determine based on hand + }, + + // buttonId + // 0 - trackpad + // 1 - triggeri + mapping: { + axis0: 'trackpad', + axis1: 'trackpad', + button0: 'trackpad', + button1: 'trigger' + }, + + bindMethods: function () { + this.onModelLoaded = bind(this.onModelLoaded, this); + this.onControllersUpdate = bind(this.onControllersUpdate, this); + this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); + this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); + this.onGamepadConnected = bind(this.onGamepadConnected, this); + this.onGamepadDisconnected = bind(this.onGamepadDisconnected, this); + }, + + init: function () { + var self = this; + this.animationActive = 'pointing'; + this.onButtonDown = function (evt) { self.onButtonEvent(evt.detail.id, 'down'); }; + this.onButtonUp = function (evt) { self.onButtonEvent(evt.detail.id, 'up'); }; + this.onButtonTouchStart = function (evt) { self.onButtonEvent(evt.detail.id, 'touchstart'); }; + this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt.detail.id, 'touchend'); }; + this.onAxisMoved = bind(this.onAxisMoved, this); + this.controllerPresent = false; + this.everGotGamepadEvent = false; + this.lastControllerCheck = 0; + this.bindMethods(); + this.isControllerPresent = isControllerPresent; // to allow mock + }, + + addEventListeners: function () { + var el = this.el; + el.addEventListener('buttondown', this.onButtonDown); + el.addEventListener('buttonup', this.onButtonUp); + el.addEventListener('touchstart', this.onButtonTouchStart); + el.addEventListener('touchend', this.onButtonTouchEnd); + el.addEventListener('model-loaded', this.onModelLoaded); + el.addEventListener('axismove', this.onAxisMoved); + }, + + removeEventListeners: function () { + var el = this.el; + el.removeEventListener('buttondown', this.onButtonDown); + el.removeEventListener('buttonup', this.onButtonUp); + el.removeEventListener('touchstart', this.onButtonTouchStart); + el.removeEventListener('touchend', this.onButtonTouchEnd); + el.removeEventListener('model-loaded', this.onModelLoaded); + el.removeEventListener('axismove', this.onAxisMoved); + }, + + checkIfControllerPresent: function () { + var isPresent = this.isControllerPresent(this.el.sceneEl, GAMEPAD_ID_PREFIX, {hand: this.data.hand}); + if (isPresent === this.controllerPresent) { return; } + this.controllerPresent = isPresent; + if (isPresent) { this.injectTrackedControls(); } // inject track-controls + }, + + onGamepadConnected: function (evt) { + // for now, don't disable controller update listening, due to + // apparent issue with FF Nightly only sending one event and seeing one controller; + // this.everGotGamepadEvent = true; + // this.removeControllersUpdateListener(); + this.checkIfControllerPresent(); + }, + + onGamepadDisconnected: function (evt) { + // for now, don't disable controller update listening, due to + // apparent issue with FF Nightly only sending one event and seeing one controller; + // this.everGotGamepadEvent = true; + // this.removeControllersUpdateListener(); + this.checkIfControllerPresent(); + }, + + play: function () { + this.checkIfControllerPresent(); + window.addEventListener('gamepadconnected', this.onGamepadConnected, false); + window.addEventListener('gamepaddisconnected', this.onGamepadDisconnected, false); + this.addControllersUpdateListener(); + this.addEventListeners(); + }, + + pause: function () { + window.removeEventListener('gamepadconnected', this.onGamepadConnected, false); + window.removeEventListener('gamepaddisconnected', this.onGamepadDisconnected, false); + this.removeControllersUpdateListener(); + this.removeEventListeners(); + }, + + injectTrackedControls: function () { + var el = this.el; + var data = this.data; + el.setAttribute('tracked-controls', {idPrefix: GAMEPAD_ID_PREFIX, hand: data.hand, rotationOffset: data.rotationOffset}); + if (!this.data.model) { return; } + this.el.setAttribute('obj-model', { + obj: GEARVR_CONTROLLER_MODEL_OBJ_URL, + mtl: GEARVR_CONTROLLER_MODEL_OBJ_MTL + }); + }, + + addControllersUpdateListener: function () { + this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false); + }, + + removeControllersUpdateListener: function () { + this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false); + }, + + onControllersUpdate: function () { + if (!this.everGotGamepadEvent) { this.checkIfControllerPresent(); } + }, + + // No need for onButtonChanged, since Daydream controller has no analog buttons. + + onModelLoaded: function (evt) { + var controllerObject3D = evt.detail.model; + var buttonMeshes; + if (!this.data.model) { return; } + buttonMeshes = this.buttonMeshes = {}; + buttonMeshes.trigger = controllerObject3D.getObjectByName('AppButton_AppButton_Cylinder.004'); + buttonMeshes.trackpad = controllerObject3D.getObjectByName('TouchPad_TouchPad_Cylinder.003'); + // Offset pivot point + controllerObject3D.position.set(0, 0, -0.04); + }, + + onAxisMoved: function (evt) { + if (evt.detail.axis[0] === 0 && evt.detail.axis[1] === 0) { return; } + this.el.emit('trackpadmoved', { x: evt.detail.axis[0], y: evt.detail.axis[1] }); + }, + + onButtonEvent: function (id, evtName) { + var buttonName = this.mapping['button' + id]; + var i; + if (Array.isArray(buttonName)) { + for (i = 0; i < buttonName.length; i++) { + this.el.emit(buttonName[i] + evtName); + } + } else { + this.el.emit(buttonName + evtName); + } + this.updateModel(buttonName, evtName); + }, + + updateModel: function (buttonName, evtName) { + var i; + if (!this.data.model) { return; } + if (Array.isArray(buttonName)) { + for (i = 0; i < buttonName.length; i++) { + this.updateButtonModel(buttonName[i], evtName); + } + } else { + this.updateButtonModel(buttonName, evtName); + } + }, + + updateButtonModel: function (buttonName, state) { + var buttonMeshes = this.buttonMeshes; + if (!buttonMeshes || !buttonMeshes[buttonName]) { return; } + var color; + switch (state) { + case 'down': + color = this.data.buttonHighlightColor; + break; + case 'touchstart': + color = this.data.buttonTouchedColor; + break; + default: + color = this.data.buttonColor; + } + buttonMeshes[buttonName].material.color.set(color); + } +}); diff --git a/src/components/hand-controls.js b/src/components/hand-controls.js index b8fbdc49da7..00fa137ac7e 100644 --- a/src/components/hand-controls.js +++ b/src/components/hand-controls.js @@ -157,6 +157,7 @@ module.exports.Component = registerComponent('hand-controls', { el.setAttribute('vive-controls', controlConfiguration); el.setAttribute('oculus-touch-controls', controlConfiguration); el.setAttribute('daydream-controls', controlConfiguration); + el.setAttribute('gearvr-controls', controlConfiguration); // Set model. el.setAttribute('blend-character-model', MODEL_URLS[hand]); diff --git a/src/components/index.js b/src/components/index.js index aeb29bb30f7..4a3df262f78 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -3,6 +3,7 @@ require('./camera'); require('./collada-model'); require('./cursor'); require('./daydream-controls'); +require('./gearvr-controls'); require('./geometry'); require('./gltf-model'); require('./hand-controls'); From a3e96c97690283e56cf82428ae7abe9fcf1fb268 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Mon, 10 Apr 2017 21:37:53 -0400 Subject: [PATCH 16/20] per discussion on 3DOF controllers, accept either hand by default --- src/components/gearvr-controls.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/gearvr-controls.js b/src/components/gearvr-controls.js index da850b891b2..fa7be093dc2 100644 --- a/src/components/gearvr-controls.js +++ b/src/components/gearvr-controls.js @@ -2,8 +2,6 @@ var registerComponent = require('../core/component').registerComponent; var bind = require('../utils/bind'); var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; -var DEFAULT_HANDEDNESS = require('../constants').DEFAULT_HANDEDNESS; - var GEARVR_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/google/'; var GEARVR_CONTROLLER_MODEL_OBJ_URL = GEARVR_CONTROLLER_MODEL_BASE_URL + 'vr_controller_daydream.obj'; var GEARVR_CONTROLLER_MODEL_OBJ_MTL = GEARVR_CONTROLLER_MODEL_BASE_URL + 'vr_controller_daydream.mtl'; @@ -18,7 +16,7 @@ var GAMEPAD_ID_PREFIX = 'Gear VR'; */ module.exports.Component = registerComponent('gearvr-controls', { schema: { - hand: {default: DEFAULT_HANDEDNESS}, // This informs the degenerate arm model. + hand: {default: ''}, // This informs the degenerate arm model. buttonColor: {type: 'color', default: '#000000'}, buttonTouchedColor: {type: 'color', default: '#777777'}, buttonHighlightColor: {type: 'color', default: '#FFFFFF'}, @@ -81,7 +79,7 @@ module.exports.Component = registerComponent('gearvr-controls', { }, checkIfControllerPresent: function () { - var isPresent = this.isControllerPresent(this.el.sceneEl, GAMEPAD_ID_PREFIX, {hand: this.data.hand}); + var isPresent = this.isControllerPresent(this.el.sceneEl, GAMEPAD_ID_PREFIX, this.data.hand ? {hand: this.data.hand} : {}); if (isPresent === this.controllerPresent) { return; } this.controllerPresent = isPresent; if (isPresent) { this.injectTrackedControls(); } // inject track-controls @@ -121,7 +119,7 @@ module.exports.Component = registerComponent('gearvr-controls', { injectTrackedControls: function () { var el = this.el; var data = this.data; - el.setAttribute('tracked-controls', {idPrefix: GAMEPAD_ID_PREFIX, hand: data.hand, rotationOffset: data.rotationOffset}); + el.setAttribute('tracked-controls', {idPrefix: GAMEPAD_ID_PREFIX, rotationOffset: data.rotationOffset}); if (!this.data.model) { return; } this.el.setAttribute('obj-model', { obj: GEARVR_CONTROLLER_MODEL_OBJ_URL, From 10ae08509e93a5d3f474aee20b6693602717dcac Mon Sep 17 00:00:00 2001 From: machenmusik Date: Mon, 10 Apr 2017 21:54:12 -0400 Subject: [PATCH 17/20] update docs to reflect default to either hand --- docs/components/gearvr-controls.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/components/gearvr-controls.md b/docs/components/gearvr-controls.md index 3b4d50a3f54..bdd65763dc3 100644 --- a/docs/components/gearvr-controls.md +++ b/docs/components/gearvr-controls.md @@ -15,6 +15,10 @@ and/or pressed buttons (trackpad, trigger). ## Example ```html + + + + ``` @@ -26,7 +30,7 @@ and/or pressed buttons (trackpad, trigger). | buttonColor | Button colors when not pressed. | #000000 | | buttonTouchedColor | Button colors when touched. | #777777 | | buttonHighlightColor | Button colors when pressed and active. | #FFFFFF | -| hand | The hand that will be tracked (i.e., right, left). | right | +| hand | The hand that will be tracked (e.g., right, left). | | | model | Whether the Daydream controller model is loaded. | true | | rotationOffset | Offset to apply to model rotation. | 0 | From 16ba6de892c14be01e29ed4405f9845f66008b45 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Thu, 13 Apr 2017 23:02:20 -0400 Subject: [PATCH 18/20] update to use proper model, thanks @mkeblx --- src/components/gearvr-controls.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/gearvr-controls.js b/src/components/gearvr-controls.js index fa7be093dc2..5b12ce37fb8 100644 --- a/src/components/gearvr-controls.js +++ b/src/components/gearvr-controls.js @@ -2,9 +2,9 @@ var registerComponent = require('../core/component').registerComponent; var bind = require('../utils/bind'); var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; -var GEARVR_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/google/'; -var GEARVR_CONTROLLER_MODEL_OBJ_URL = GEARVR_CONTROLLER_MODEL_BASE_URL + 'vr_controller_daydream.obj'; -var GEARVR_CONTROLLER_MODEL_OBJ_MTL = GEARVR_CONTROLLER_MODEL_BASE_URL + 'vr_controller_daydream.mtl'; +var GEARVR_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/samsung/'; +var GEARVR_CONTROLLER_MODEL_OBJ_URL = GEARVR_CONTROLLER_MODEL_BASE_URL + 'gear_vr_controller.obj'; +var GEARVR_CONTROLLER_MODEL_OBJ_MTL = GEARVR_CONTROLLER_MODEL_BASE_URL + 'gear_vr_controller.mtl'; var GAMEPAD_ID_PREFIX = 'Gear VR'; @@ -146,10 +146,8 @@ module.exports.Component = registerComponent('gearvr-controls', { var buttonMeshes; if (!this.data.model) { return; } buttonMeshes = this.buttonMeshes = {}; - buttonMeshes.trigger = controllerObject3D.getObjectByName('AppButton_AppButton_Cylinder.004'); - buttonMeshes.trackpad = controllerObject3D.getObjectByName('TouchPad_TouchPad_Cylinder.003'); - // Offset pivot point - controllerObject3D.position.set(0, 0, -0.04); + buttonMeshes.trigger = controllerObject3D.getObjectByName('Trigger'); + buttonMeshes.trackpad = controllerObject3D.getObjectByName('Touchpad'); }, onAxisMoved: function (evt) { From 0dd0da0cb784e31bc89d303a37a375873244d521 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Thu, 13 Apr 2017 22:52:41 -0400 Subject: [PATCH 19/20] bring gearvr-controls in line with #2513 --- src/components/gearvr-controls.js | 67 +++++++++++++++++-------------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/src/components/gearvr-controls.js b/src/components/gearvr-controls.js index 5b12ce37fb8..d876427e20c 100644 --- a/src/components/gearvr-controls.js +++ b/src/components/gearvr-controls.js @@ -28,19 +28,21 @@ module.exports.Component = registerComponent('gearvr-controls', { // 0 - trackpad // 1 - triggeri mapping: { - axis0: 'trackpad', - axis1: 'trackpad', - button0: 'trackpad', - button1: 'trigger' + axes: {'trackpad': [0, 1]}, + buttons: ['trackpad', 'trigger'] }, + // Use these labels for detail on axis events such as thumbstickmoved. + // e.g. for thumbstickmoved detail, the first axis returned is labeled x, and the second is labeled y. + axisLabels: ['x', 'y', 'z', 'w'], + bindMethods: function () { this.onModelLoaded = bind(this.onModelLoaded, this); this.onControllersUpdate = bind(this.onControllersUpdate, this); this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this); this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this); - this.onGamepadConnected = bind(this.onGamepadConnected, this); - this.onGamepadDisconnected = bind(this.onGamepadDisconnected, this); + this.onAxisMoved = bind(this.onAxisMoved, this); + this.onGamepadConnectionEvent = bind(this.onGamepadConnectionEvent, this); }, init: function () { @@ -85,33 +87,21 @@ module.exports.Component = registerComponent('gearvr-controls', { if (isPresent) { this.injectTrackedControls(); } // inject track-controls }, - onGamepadConnected: function (evt) { - // for now, don't disable controller update listening, due to - // apparent issue with FF Nightly only sending one event and seeing one controller; - // this.everGotGamepadEvent = true; - // this.removeControllersUpdateListener(); - this.checkIfControllerPresent(); - }, - - onGamepadDisconnected: function (evt) { - // for now, don't disable controller update listening, due to - // apparent issue with FF Nightly only sending one event and seeing one controller; - // this.everGotGamepadEvent = true; - // this.removeControllersUpdateListener(); + onGamepadConnectionEvent: function (evt) { this.checkIfControllerPresent(); }, play: function () { this.checkIfControllerPresent(); - window.addEventListener('gamepadconnected', this.onGamepadConnected, false); - window.addEventListener('gamepaddisconnected', this.onGamepadDisconnected, false); + // Note that due to gamepadconnected event propagation issues, we don't rely on events. + window.addEventListener('gamepaddisconnected', this.checkIfControllerPresent, false); this.addControllersUpdateListener(); this.addEventListeners(); }, pause: function () { - window.removeEventListener('gamepadconnected', this.onGamepadConnected, false); - window.removeEventListener('gamepaddisconnected', this.onGamepadDisconnected, false); + // Note that due to gamepadconnected event propagation issues, we don't rely on events. + window.removeEventListener('gamepaddisconnected', this.checkIfControllerPresent, false); this.removeControllersUpdateListener(); this.removeEventListeners(); }, @@ -136,10 +126,11 @@ module.exports.Component = registerComponent('gearvr-controls', { }, onControllersUpdate: function () { - if (!this.everGotGamepadEvent) { this.checkIfControllerPresent(); } + // Note that due to gamepadconnected event propagation issues, we don't rely on events. + this.checkIfControllerPresent(); }, - // No need for onButtonChanged, since Daydream controller has no analog buttons. + // No need for onButtonChanged, since Gear VR controller has no analog buttons. onModelLoaded: function (evt) { var controllerObject3D = evt.detail.model; @@ -150,13 +141,8 @@ module.exports.Component = registerComponent('gearvr-controls', { buttonMeshes.trackpad = controllerObject3D.getObjectByName('Touchpad'); }, - onAxisMoved: function (evt) { - if (evt.detail.axis[0] === 0 && evt.detail.axis[1] === 0) { return; } - this.el.emit('trackpadmoved', { x: evt.detail.axis[0], y: evt.detail.axis[1] }); - }, - onButtonEvent: function (id, evtName) { - var buttonName = this.mapping['button' + id]; + var buttonName = this.mapping.buttons[id]; var i; if (Array.isArray(buttonName)) { for (i = 0; i < buttonName.length; i++) { @@ -168,6 +154,25 @@ module.exports.Component = registerComponent('gearvr-controls', { this.updateModel(buttonName, evtName); }, + onAxisMoved: function (evt) { + var self = this; + var axesMapping = this.mapping.axes; + // In theory, it might be better to use mapping from axis to control. + // In practice, it is not clear whether the additional overhead is worthwhile, + // and if we did grouping of axes, we really need de-duplication there. + Object.keys(axesMapping).forEach(function (key) { + var value = axesMapping[key]; + var detail = {}; + var changed = !evt.detail.changed; + if (!changed) { value.forEach(function (axisNumber) { changed |= evt.detail.changed[axisNumber]; }); } + if (changed) { + value.forEach(function (axisNumber) { detail[self.axisLabels[axisNumber]] = evt.detail.axis[axisNumber]; }); + self.el.emit(key + 'moved', detail); + // If we updated the model based on axis values, that call would go here. + } + }); + }, + updateModel: function (buttonName, evtName) { var i; if (!this.data.model) { return; } From 2c3c3a467058d5c2b5d7d844f202b77c1bb17ca3 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Fri, 14 Apr 2017 14:53:09 -0400 Subject: [PATCH 20/20] refactor emitIfAxesChanged into utils --- src/components/daydream-controls.js | 22 +++------------------- src/components/gearvr-controls.js | 21 +++------------------ src/components/oculus-touch-controls.js | 25 +++---------------------- src/components/vive-controls.js | 21 +++------------------ src/utils/tracked-controls.js | 23 +++++++++++++++++++++++ 5 files changed, 35 insertions(+), 77 deletions(-) diff --git a/src/components/daydream-controls.js b/src/components/daydream-controls.js index e8b8c8a28cd..68ffc2ca6a7 100644 --- a/src/components/daydream-controls.js +++ b/src/components/daydream-controls.js @@ -1,6 +1,7 @@ var registerComponent = require('../core/component').registerComponent; var bind = require('../utils/bind'); var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; +var emitIfAxesChanged = require('../utils/tracked-controls').emitIfAxesChanged; var DAYDREAM_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/google/'; var DAYDREAM_CONTROLLER_MODEL_OBJ_URL = DAYDREAM_CONTROLLER_MODEL_BASE_URL + 'vr_controller_daydream.obj'; @@ -59,6 +60,7 @@ module.exports.Component = registerComponent('daydream-controls', { this.lastControllerCheck = 0; this.bindMethods(); this.isControllerPresent = isControllerPresent; // to allow mock + this.emitIfAxesChanged = emitIfAxesChanged; // to allow mock }, addEventListeners: function () { @@ -149,25 +151,7 @@ module.exports.Component = registerComponent('daydream-controls', { controllerObject3D.position.set(0, 0, -0.04); }, - onAxisMoved: function (evt) { - var self = this; - var axesMapping = this.mapping.axes; - - // In theory, it might be better to use mapping from axis to control. - // In practice, it is not clear whether the additional overhead is worthwhile, - // and if we did grouping of axes, we really need de-duplication there. - Object.keys(axesMapping).forEach(function (key) { - var value = axesMapping[key]; - var detail = {}; - var changed = !evt.detail.changed; - if (!changed) { value.forEach(function (axisNumber) { changed |= evt.detail.changed[axisNumber]; }); } - if (changed) { - value.forEach(function (axisNumber) { detail[self.axisLabels[axisNumber]] = evt.detail.axis[axisNumber]; }); - self.el.emit(key + 'moved', detail); - // If we updated the model based on axis values, that call would go here. - } - }); - }, + onAxisMoved: function (evt) { this.emitIfAxesChanged(this, this.mapping.axes, evt); }, onButtonEvent: function (id, evtName) { var buttonName = this.mapping.buttons[id]; diff --git a/src/components/gearvr-controls.js b/src/components/gearvr-controls.js index d876427e20c..06ac57525cd 100644 --- a/src/components/gearvr-controls.js +++ b/src/components/gearvr-controls.js @@ -1,6 +1,7 @@ var registerComponent = require('../core/component').registerComponent; var bind = require('../utils/bind'); var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; +var emitIfAxesChanged = require('../utils/tracked-controls').emitIfAxesChanged; var GEARVR_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/samsung/'; var GEARVR_CONTROLLER_MODEL_OBJ_URL = GEARVR_CONTROLLER_MODEL_BASE_URL + 'gear_vr_controller.obj'; @@ -58,6 +59,7 @@ module.exports.Component = registerComponent('gearvr-controls', { this.lastControllerCheck = 0; this.bindMethods(); this.isControllerPresent = isControllerPresent; // to allow mock + this.emitIfAxesChanged = emitIfAxesChanged; // to allow mock }, addEventListeners: function () { @@ -154,24 +156,7 @@ module.exports.Component = registerComponent('gearvr-controls', { this.updateModel(buttonName, evtName); }, - onAxisMoved: function (evt) { - var self = this; - var axesMapping = this.mapping.axes; - // In theory, it might be better to use mapping from axis to control. - // In practice, it is not clear whether the additional overhead is worthwhile, - // and if we did grouping of axes, we really need de-duplication there. - Object.keys(axesMapping).forEach(function (key) { - var value = axesMapping[key]; - var detail = {}; - var changed = !evt.detail.changed; - if (!changed) { value.forEach(function (axisNumber) { changed |= evt.detail.changed[axisNumber]; }); } - if (changed) { - value.forEach(function (axisNumber) { detail[self.axisLabels[axisNumber]] = evt.detail.axis[axisNumber]; }); - self.el.emit(key + 'moved', detail); - // If we updated the model based on axis values, that call would go here. - } - }); - }, + onAxisMoved: function (evt) { this.emitIfAxesChanged(this, this.mapping.axes, evt); }, updateModel: function (buttonName, evtName) { var i; diff --git a/src/components/oculus-touch-controls.js b/src/components/oculus-touch-controls.js index 0eca41d1af6..9527c1ecc65 100644 --- a/src/components/oculus-touch-controls.js +++ b/src/components/oculus-touch-controls.js @@ -1,7 +1,7 @@ var registerComponent = require('../core/component').registerComponent; var bind = require('../utils/bind'); -var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; var getGamepadsByPrefix = require('../utils/tracked-controls').getGamepadsByPrefix; +var emitIfAxesChanged = require('../utils/tracked-controls').emitIfAxesChanged; var TOUCH_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/oculus/oculus-touch-controller-'; var TOUCH_CONTROLLER_MODEL_OBJ_URL_L = TOUCH_CONTROLLER_MODEL_BASE_URL + 'left.obj'; @@ -70,8 +70,8 @@ module.exports.Component = registerComponent('oculus-touch-controls', { this.lastControllerCheck = 0; this.previousButtonValues = {}; this.bindMethods(); - this.isControllerPresent = isControllerPresent; // to allow mock this.getGamepadsByPrefix = getGamepadsByPrefix; // to allow mock + this.emitIfAxesChanged = emitIfAxesChanged; // to allow mock }, addEventListeners: function () { @@ -224,26 +224,7 @@ module.exports.Component = registerComponent('oculus-touch-controls', { this.updateModel(buttonName, evtName); }, - onAxisMoved: function (evt) { - var self = this; - var axesMapping = this.mapping[this.data.hand].axes; - // In theory, it might be better to use mapping from axis to control. - // In practice, it is not clear whether the additional overhead is worthwhile, - // and if we did grouping of axes, we really need de-duplication there. - Object.keys(axesMapping).forEach(function (key) { - var value = axesMapping[key]; - var detail = {}; - var changed = !evt.detail.changed; - if (!changed) { - value.forEach(function (axisNumber) { changed |= evt.detail.changed[axisNumber]; }); - } - if (changed) { - value.forEach(function (axisNumber) { detail[self.axisLabels[axisNumber]] = evt.detail.axis[axisNumber]; }); - self.el.emit(key + 'moved', detail); - // If we updated the model based on axis values, that call would go here. - } - }); - }, + onAxisMoved: function (evt) { this.emitIfAxesChanged(this, this.mapping[this.data.hand].axes, evt); }, updateModel: function (buttonName, evtName) { var i; diff --git a/src/components/vive-controls.js b/src/components/vive-controls.js index e6c86cc2f1a..5b092fe5848 100644 --- a/src/components/vive-controls.js +++ b/src/components/vive-controls.js @@ -1,6 +1,7 @@ var registerComponent = require('../core/component').registerComponent; var bind = require('../utils/bind'); var isControllerPresent = require('../utils/tracked-controls').isControllerPresent; +var emitIfAxesChanged = require('../utils/tracked-controls').emitIfAxesChanged; var VIVE_CONTROLLER_MODEL_OBJ_URL = 'https://cdn.aframe.io/controllers/vive/vr_controller_vive.obj'; var VIVE_CONTROLLER_MODEL_OBJ_MTL = 'https://cdn.aframe.io/controllers/vive/vr_controller_vive.mtl'; @@ -59,6 +60,7 @@ module.exports.Component = registerComponent('vive-controls', { this.previousButtonValues = {}; this.bindMethods(); this.isControllerPresent = isControllerPresent; // to allow mock + this.emitIfAxesChanged = emitIfAxesChanged; // to allow mock }, addEventListeners: function () { @@ -171,24 +173,7 @@ module.exports.Component = registerComponent('vive-controls', { controllerObject3D.position.set(0, -0.015, 0.04); }, - onAxisMoved: function (evt) { - var self = this; - var axesMapping = this.mapping.axes; - // In theory, it might be better to use mapping from axis to control. - // In practice, it is not clear whether the additional overhead is worthwhile, - // and if we did grouping of axes, we really need de-duplication there. - Object.keys(axesMapping).forEach(function (key) { - var value = axesMapping[key]; - var detail = {}; - var changed = !evt.detail.changed; - if (!changed) { value.forEach(function (axisNumber) { changed |= evt.detail.changed[axisNumber]; }); } - if (changed) { - value.forEach(function (axisNumber) { detail[self.axisLabels[axisNumber]] = evt.detail.axis[axisNumber]; }); - self.el.emit(key + 'moved', detail); - // If we updated the model based on axis values, that call would go here. - } - }); - }, + onAxisMoved: function (evt) { this.emitIfAxesChanged(this, this.mapping.axes, evt); }, onButtonEvent: function (id, evtName) { var buttonName = this.mapping.buttons[id]; diff --git a/src/utils/tracked-controls.js b/src/utils/tracked-controls.js index eba5f6d08be..5ad699a45e7 100644 --- a/src/utils/tracked-controls.js +++ b/src/utils/tracked-controls.js @@ -1,4 +1,5 @@ var DEFAULT_HANDEDNESS = require('../constants').DEFAULT_HANDEDNESS; +var AXIS_LABELS = ['x', 'y', 'z', 'w']; /** * Return enumerated gamepads matching id prefix. @@ -60,3 +61,25 @@ module.exports.isControllerPresent = function (sceneEl, idPrefix, queryObject) { } return isPresent; }; + +/** + * Emit specific moved event(s) if axes changed, based on original axismoved event. + * + * @param {object} self - the component in use (e.g. oculus-touch-controls, vive-controls...) + * @param {array} axesMapping - the axes mapping to process + * @param {object} evt - the event to process + */ +module.exports.emitIfAxesChanged = function (self, axesMapping, evt) { + Object.keys(axesMapping).forEach(function (key) { + var axes = axesMapping[key]; + var changed = evt.detail.changed; + // If no changed axes given at all, or at least one changed value is true in the array, + if (axes.reduce(function (b, axis) { return b || changed[axis]; }, !changed)) { + // An axis has changed, so emit the specific moved event, detailing axis values. + var detail = {}; + axes.forEach(function (axis) { detail[AXIS_LABELS[axis]] = evt.detail.axis[axis]; }); + self.el.emit(key + 'moved', detail); + // If we updated the model based on axis values, that call would go here. + } + }); +};