Skip to content

Commit

Permalink
Switch unit tests to WebXR and fix vive(-focus)-controls (#5452)
Browse files Browse the repository at this point in the history
Co-authored-by: Noeri Huisman <mrxz@users.noreply.github.com>
  • Loading branch information
mrxz and mrxz committed Feb 23, 2024
1 parent 27dae6a commit dd3913c
Show file tree
Hide file tree
Showing 12 changed files with 156 additions and 369 deletions.
9 changes: 5 additions & 4 deletions src/components/vive-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var isWebXRAvailable = require('../utils/').device.isWebXRAvailable;
var GAMEPAD_ID_WEBXR = 'htc-vive';
var GAMEPAD_ID_WEBVR = 'OpenVR ';

// Prefix for Gen1 and Gen2 Oculus Touch Controllers.
// Prefix for HTC Vive Controllers.
var GAMEPAD_ID_PREFIX = isWebXRAvailable ? GAMEPAD_ID_WEBXR : GAMEPAD_ID_WEBVR;

/**
Expand Down Expand Up @@ -44,16 +44,16 @@ var INPUT_MAPPING_WEBVR = {
* Reference: https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/htc/htc-vive.json
*/
var INPUT_MAPPING_WEBXR = {
axes: {thumbstick: [0, 1]},
buttons: ['trigger', 'grip', 'trackpad', 'none', 'menu']
axes: {touchpad: [0, 1]},
buttons: ['trigger', 'grip', 'touchpad', 'none']
};

var INPUT_MAPPING = isWebXRAvailable ? INPUT_MAPPING_WEBXR : INPUT_MAPPING_WEBVR;

/**
* Vive controls.
* Interface with Vive controllers and map Gamepad events to controller buttons:
* trackpad, trigger, grip, menu, system
* touchpad, trigger, grip, menu, system
* Load a controller model and highlight the pressed buttons.
*/
module.exports.Component = registerComponent('vive-controls', {
Expand Down Expand Up @@ -209,6 +209,7 @@ module.exports.Component = registerComponent('vive-controls', {
buttonMeshes.menu = controllerObject3D.getObjectByName('menubutton');
buttonMeshes.system = controllerObject3D.getObjectByName('systembutton');
buttonMeshes.trackpad = controllerObject3D.getObjectByName('touchpad');
buttonMeshes.touchpad = controllerObject3D.getObjectByName('touchpad');
buttonMeshes.trigger = controllerObject3D.getObjectByName('trigger');

// Set default colors.
Expand Down
43 changes: 32 additions & 11 deletions src/components/vive-focus-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,40 @@ var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresent
var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged;
var onButtonEvent = trackedControlsUtils.onButtonEvent;

var GAMEPAD_ID_PREFIX = 'HTC Vive Focus';

var AFRAME_CDN_ROOT = require('../constants').AFRAME_CDN_ROOT;
var VIVE_FOCUS_CONTROLLER_MODEL_URL = AFRAME_CDN_ROOT + 'controllers/vive/focus-controller/focus-controller.gltf';

var isWebXRAvailable = require('../utils/').device.isWebXRAvailable;

var GAMEPAD_ID_WEBXR = 'htc-vive-focus';
var GAMEPAD_ID_WEBVR = 'HTC Vive Focus ';

// Prefix for HTC Vive Focus Controllers.
var GAMEPAD_ID_PREFIX = isWebXRAvailable ? GAMEPAD_ID_WEBXR : GAMEPAD_ID_WEBVR;

/**
* Button IDs:
* 0 - trackpad
* 1 - trigger
*/
var INPUT_MAPPING_WEBVR = {
axes: {trackpad: [0, 1]},
buttons: ['trackpad', 'trigger']
};

/**
* Button IDs:
* 0 - trigger
* 2 - touchpad
* 4 - menu
*/
var INPUT_MAPPING_WEBXR = {
axes: {touchpad: [0, 1]},
buttons: ['trigger', 'none', 'touchpad', 'none', 'menu']
};

var INPUT_MAPPING = isWebXRAvailable ? INPUT_MAPPING_WEBXR : INPUT_MAPPING_WEBVR;

/**
* Vive Focus controls.
* Interface with Vive Focus controller and map Gamepad events to
Expand All @@ -26,15 +55,7 @@ module.exports.Component = registerComponent('vive-focus-controls', {
armModel: {default: true}
},

/**
* Button IDs:
* 0 - trackpad
* 1 - trigger
*/
mapping: {
axes: {trackpad: [0, 1]},
buttons: ['trackpad', 'trigger']
},
mapping: INPUT_MAPPING,

bindMethods: function () {
this.onModelLoaded = this.onModelLoaded.bind(this);
Expand Down
2 changes: 1 addition & 1 deletion src/utils/device.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ var supportsARSession = false;
* Oculus Browser 7 doesn't support the WebXR gamepads module.
* We fallback to WebVR API and will hotfix when implementation is complete.
*/
var isWebXRAvailable = module.exports.isWebXRAvailable = !window.debug && navigator.xr !== undefined;
var isWebXRAvailable = module.exports.isWebXRAvailable = navigator.xr !== undefined;

// Catch vrdisplayactivate early to ensure we can enter VR mode after the scene loads.
window.addEventListener('vrdisplayactivate', function (evt) {
Expand Down
15 changes: 13 additions & 2 deletions tests/__init.test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
/* global sinon, setup, teardown */
/* global EventTarget, sinon, setup, teardown */

/**
* __init.test.js is run before every test case.
*/
window.debug = true;

/* WebVR Stub */
navigator.getVRDisplays = function () {
var resolvePromise = Promise.resolve();
var mockVRDisplay = {
Expand All @@ -19,6 +20,16 @@ navigator.getVRDisplays = function () {
return Promise.resolve([mockVRDisplay]);
};

/* WebXR Stub */
navigator.xr = navigator.xr || {};
navigator.xr.isSessionSupported = function (_sessionType) { return Promise.resolve(true); };
navigator.xr.requestSession = function (_mode) {
const xrSession = new EventTarget();
xrSession.supportedFrameRates = [90];
xrSession.requestReferenceSpace = function () { return Promise.resolve(); };
return Promise.resolve(xrSession);
};

const AFRAME = require('index');
var AScene = require('core/scene/a-scene').AScene;
// Make sure WebGL context is not created since CI runs headless.
Expand All @@ -28,7 +39,7 @@ AScene.prototype.setupRenderer = function () {};
setup(function () {
window.AFRAME = AFRAME;
this.sinon = sinon.createSandbox();
// Stubs to not create a WebGL context since Travis CI runs headless.
// Stubs to not create a WebGL context since CI runs headless.
this.sinon.stub(AScene.prototype, 'render');
this.sinon.stub(AScene.prototype, 'setupRenderer');
// Mock renderer.
Expand Down
85 changes: 11 additions & 74 deletions tests/components/oculus-go-controls.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,9 @@ suite('oculus-go-controls', function () {
component.controllers = [];
// Our Mock data for enabling the controllers.
component.controllersWhenPresent = [{
id: 'Oculus Go Controller',
index: 0,
hand: 'right',
axes: [0, 0],
buttons: [
{value: 0, pressed: false, touched: false},
{value: 0, pressed: false, touched: false}
],
pose: {orientation: [1, 0, 0, 0], position: null}
profiles: ['oculus-go'],
handedness: 'right'
}];
el.parentEl.renderer.xr.getStandingMatrix = function () {};
done();
};
if (el.hasLoaded) { callback(); }
Expand All @@ -37,7 +29,7 @@ suite('oculus-go-controls', function () {

setup(function (done) {
component = this.el.components['oculus-go-controls'];
controllerSystem = this.el.sceneEl.systems['tracked-controls-webvr'];
controllerSystem = this.el.sceneEl.systems['tracked-controls-webxr'];
addEventListenersSpy = sinon.spy(component, 'addEventListeners');
injectTrackedControlsSpy = sinon.spy(component, 'injectTrackedControls');
removeEventListenersSpy = sinon.spy(component, 'removeEventListeners');
Expand Down Expand Up @@ -117,14 +109,14 @@ suite('oculus-go-controls', function () {
});

suite('axismove', function () {
test('emits trackpadmoved on axismove', function (done) {
test('emits touchpadmoved on axismove', function (done) {
var el = this.el;
setupTestControllers(el);

// Configure the event state for which we'll use the axis state for verification.
const eventState = {axis: [0.1, 0.2], changed: [true, false]};

el.addEventListener('trackpadmoved', function (evt) {
el.addEventListener('touchpadmoved', function (evt) {
assert.equal(evt.detail.x, eventState.axis[0]);
assert.equal(evt.detail.y, eventState.axis[1]);
done();
Expand All @@ -133,13 +125,13 @@ suite('oculus-go-controls', function () {
el.emit('axismove', eventState);
});

test('does not emit trackpadmoved on axismove with no changes', function (done) {
test('does not emit touchpadmoved on axismove with no changes', function (done) {
var el = this.el;
setupTestControllers(el);

// Fail purposely.
el.addEventListener('trackpadmoved', function (evt) {
assert.fail('trackpadmoved was called when there was no change.');
el.addEventListener('touchpadmoved', function (evt) {
assert.fail('touchpadmoved was called when there was no change.');
});

el.emit('axismove', {axis: [0.1, 0.2], changed: [false, false]});
Expand All @@ -148,8 +140,8 @@ suite('oculus-go-controls', function () {
});

suite('buttonchanged', function () {
[{ button: 'trackpad', id: 0 },
{ button: 'trigger', id: 1 }
[{ button: 'trigger', id: 0 },
{ button: 'touchpad', id: 2 }
].forEach(function (buttonDescription) {
test('if we get buttonchanged for button ' + buttonDescription.id + ', emit ' + buttonDescription.button + 'changed', function (done) {
var el = this.el;
Expand Down Expand Up @@ -190,69 +182,14 @@ suite('oculus-go-controls', function () {
});
});

suite('armModel', function () {
test('does not apply armModel if armModel disabled', function () {
var el = this.el;
el.setAttribute('oculus-go-controls', 'armModel', false);
setupTestControllers(el);

var trackedControls = el.components['tracked-controls-webvr'];
var applyArmModelSpy = sinon.spy(trackedControls, 'applyArmModel');
trackedControls.tick();

// Verify that the function which applies arm model is not called when disabled.
sinon.assert.notCalled(applyArmModelSpy);

// Additionally verify that no other offsets have been applied.
assert.strictEqual(el.object3D.position.x, 0);
assert.strictEqual(el.object3D.position.y, 0);
assert.strictEqual(el.object3D.position.z, 0);
});

test('applies armModel if armModel enabled', function () {
var el = this.el;
el.setAttribute('oculus-go-controls', 'armModel', true);
setupTestControllers(el);

var trackedControls = el.components['tracked-controls-webvr'];
var applyArmModelSpy = sinon.spy(trackedControls, 'applyArmModel');
trackedControls.tick();

// Verify that the function which applies arm model is called.
sinon.assert.calledOnce(applyArmModelSpy);
});

test('verifies armModel position is applied for the right hand', function () {
var el = this.el;
el.setAttribute('oculus-go-controls', 'armModel', true);
setupTestControllers(el);

var trackedControls = el.components['tracked-controls-webvr'];
trackedControls.tick();
assert.ok(el.object3D.position.x > 0);
});

test('verifies armModel position is applied for the left hand', function () {
var el = this.el;
el.setAttribute('oculus-go-controls', 'armModel', true);
el.setAttribute('oculus-go-controls', 'hand', 'left');
el.components['oculus-go-controls'].controllersWhenPresent[0].hand = 'left';
setupTestControllers(el);

var trackedControls = el.components['tracked-controls-webvr'];
trackedControls.tick();
assert.ok(el.object3D.position.x < 0);
});
});

/**
* Establishes the baseline set of controllers needed for the tests to run.
*
* @param {object} el - The current entity factory.
*/
function setupTestControllers (el) {
var component = el.components['oculus-go-controls'];
el.sceneEl.systems['tracked-controls-webvr'].controllers = component.controllersWhenPresent;
el.sceneEl.systems['tracked-controls-webxr'].controllers = component.controllersWhenPresent;
component.checkIfControllerPresent();
}
});
23 changes: 10 additions & 13 deletions tests/components/oculus-touch-controls.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ suite('oculus-touch-controls', function () {
component.controllers = [];
// Our Mock data for enabling the controllers.
component.controllersWhenPresent = [{
id: 'Oculus Touch',
index: 0,
hand: 'left',
pose: {}
profiles: ['oculus-touch'],
handedness: 'left'
}];
done();
};
Expand All @@ -34,7 +32,7 @@ suite('oculus-touch-controls', function () {

setup(function (done) {
component = this.el.components['oculus-touch-controls'];
controllerSystem = this.el.sceneEl.systems['tracked-controls-webvr'];
controllerSystem = this.el.sceneEl.systems['tracked-controls-webxr'];
controllerSystem.vrDisplay = true;
addEventListenersSpy = sinon.spy(component, 'addEventListeners');
injectTrackedControlsSpy = sinon.spy(component, 'injectTrackedControls');
Expand Down Expand Up @@ -118,7 +116,7 @@ suite('oculus-touch-controls', function () {
var controllerSystem;

setup(function (done) {
controllerSystem = this.el.sceneEl.systems['tracked-controls-webvr'];
controllerSystem = this.el.sceneEl.systems['tracked-controls-webxr'];
controllerSystem.controllers = component.controllersWhenPresent;
controllerSystem.vrDisplay = true;
done();
Expand All @@ -128,11 +126,11 @@ suite('oculus-touch-controls', function () {
// Do the check.
component.checkIfControllerPresent();
// Set up the event details.
const eventDetails = {axis: [0.1, 0.2], changed: [true, false]};
const eventDetails = {axis: [0.1, 0.2, 0.3, 0.4], changed: [false, false, true, false]};
// Install event handler listening for thumbstickmoved.
this.el.addEventListener('thumbstickmoved', function (evt) {
assert.equal(evt.detail.x, eventDetails.axis[0]);
assert.equal(evt.detail.y, eventDetails.axis[1]);
assert.equal(evt.detail.x, eventDetails.axis[2]);
assert.equal(evt.detail.y, eventDetails.axis[3]);
done();
});
// Emit axismove.
Expand All @@ -147,15 +145,14 @@ suite('oculus-touch-controls', function () {
assert.fail('thumbstickmoved should not be called');
});
// Emit axismove with no changes.
this.el.emit('axismove', {axis: [0.1, 0.2], changed: [false, false]});
this.el.emit('axismove', {axis: [0.1, 0.2, 0.3, 0.4], changed: [false, false, false, false]});
setTimeout(() => { done(); });
});
});

suite('buttonchanged', function () {
test('can emit triggerchanged', function (done) {
el.sceneEl.systems['tracked-controls-webvr'].controllers = component.controllersWhenPresent;
el.sceneEl.systems['tracked-controls-webvr'].vrDisplay = true;
el.sceneEl.systems['tracked-controls-webxr'].controllers = component.controllersWhenPresent;
// Do the check.
component.checkIfControllerPresent();
// Prepare the event details
Expand All @@ -166,7 +163,7 @@ suite('oculus-touch-controls', function () {
done();
});
// Emit buttonchanged.
el.emit('buttonchanged', {id: 1, state: eventState});
el.emit('buttonchanged', {id: 0, state: eventState});
});
});
});
8 changes: 0 additions & 8 deletions tests/components/scene/xr-mode-ui.test.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
/* global assert, process, setup, suite, test */
var entityFactory = require('../../helpers').entityFactory;
var utils = require('index').utils;

var UI_CLASSES = ['.a-orientation-modal', '.a-enter-vr-button'];

suite('xr-mode-ui', function () {
setup(function (done) {
this.entityEl = entityFactory();
var el = this.el = this.entityEl.parentNode;
this.sinon.stub(utils.device, 'getVRDisplay').returns({
requestPresent: function () {
return Promise.resolve();
}
});
el.addEventListener('loaded', function () { done(); });
});

Expand All @@ -38,7 +32,6 @@ suite('xr-mode-ui', function () {
el: {object3D: {}},
updateProjectionMatrix: function () {}
};
window.hasNativeWebVRImplementation = false;
scene.enterVR();
UI_CLASSES.forEach(function (uiClass) {
assert.ok(scene.querySelector(uiClass).className.indexOf('a-hidden'));
Expand All @@ -52,7 +45,6 @@ suite('xr-mode-ui', function () {
el: {object3D: {}, getAttribute: function () { return {spectator: false}; }},
updateProjectionMatrix: function () {}
};
window.hasNativeWebVRImplementation = false;
scene.enterVR();
scene.exitVR();

Expand Down

0 comments on commit dd3913c

Please sign in to comment.