Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
fix vive-controls button colors
  • Loading branch information
ngokevin committed Jun 14, 2017
commit 20c9866d7246187b59e715fbce914c3976a9c3d9
171 changes: 102 additions & 69 deletions src/components/vive-controls.js
Original file line number Diff line number Diff line change
@@ -1,75 +1,93 @@
var registerComponent = require('../core/component').registerComponent;
var bind = require('../utils/bind');
var checkControllerPresentAndSetup = require('../utils/tracked-controls').checkControllerPresentAndSetup;
var emitIfAxesChanged = require('../utils/tracked-controls').emitIfAxesChanged;
var utils = require('../utils/');

var bind = utils.bind;
var checkControllerPresentAndSetup = utils.trackedControls.checkControllerPresentAndSetup;
var emitIfAxesChanged = utils.trackedControls.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';

var GAMEPAD_ID_PREFIX = 'OpenVR ';

/**
* 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
* Vive controls.
* Interface with Vive controllers and map Gamepad events to controller buttons:
* trackpad, trigger, grip, menu, system
* Load a controller model and highlight the pressed buttons.
*/
module.exports.Component = registerComponent('vive-controls', {
schema: {
hand: {default: 'left'},
buttonColor: {type: 'color', default: '#FAFAFA'}, // Off-white.
buttonHighlightColor: {type: 'color', default: '#22D1EE'}, // Light blue.
model: {default: true},
rotationOffset: {default: 0} // use -999 as sentinel value to auto-determine based on hand
rotationOffset: {default: 0}
},

// buttonId
// 0 - trackpad
// 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 )
/**
* Button IDs:
* 0 - trackpad
* 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)
*/
mapping: {
axes: {'trackpad': [0, 1]},
buttons: ['trackpad', 'trigger', 'grip', '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.
/**
* Labels for detail on axis events such as `thumbstickmoved`.
* For example, on `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.onAxisMoved = bind(this.onAxisMoved, this);
},

init: function () {
var self = this;
this.animationActive = 'pointing';
this.checkControllerPresentAndSetup = checkControllerPresentAndSetup; // To allow mock.
this.controllerPresent = false;
this.emitIfAxesChanged = emitIfAxesChanged; // To allow mock.
this.lastControllerCheck = 0;
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'); };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the touchpad is capacitive. Are those events not captured anymore with this PR?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I can adjust to make this continue to emit the trackpadtouchend events...but I just realized maybe the deeper issue is that doing triggerdown/triggerup causes a touchend event from tracked-controls.

Copy link
Member

@dmarcos dmarcos Jun 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might not be a bug. I think those touchstart / touchend are legit. I believe @machenmusik observed that for the first time and probably related to this https://bugzilla.mozilla.org/show_bug.cgi?id=1353523. A slight press on the trigger is considered as a touchstart event.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, it takes more than a light touch. But I updated the PR to ignore the touch events for buttons that don't have touch. trackpadtouchstart and vice-versa now continue to emit.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coming to this a little late, but IIRC for Vive, the touch start / end events were intentional for analog buttons (trigger) as well as real touch sensitive buttons (trackpad) but are not useful for digital buttons (grip, menu).

this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt.detail.id, 'touchend'); };
this.onAxisMoved = bind(this.onAxisMoved, this);
this.controllerPresent = false;
this.lastControllerCheck = 0;
this.previousButtonValues = {};

this.bindMethods();
this.checkControllerPresentAndSetup = checkControllerPresentAndSetup; // to allow mock
this.emitIfAxesChanged = emitIfAxesChanged; // to allow mock
},

play: function () {
this.checkIfControllerPresent();
this.addControllersUpdateListener();
// 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();
// Note that due to gamepadconnected event propagation issues, we don't rely on events.
window.removeEventListener('gamepaddisconnected', this.checkIfControllerPresent, false);
},

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.onAxisMoved = bind(this.onAxisMoved, this);
},

addEventListeners: function () {
var el = this.el;
el.addEventListener('buttonchanged', this.onButtonChanged);
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);
},
Expand All @@ -79,42 +97,35 @@ module.exports.Component = registerComponent('vive-controls', {
el.removeEventListener('buttonchanged', this.onButtonChanged);
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);
},

/**
* Once OpenVR returns correct hand data in supporting browsers, we can use hand property.
* var isPresent = this.checkControllerPresentAndSetup(this.el.sceneEl, GAMEPAD_ID_PREFIX,
{ hand: data.hand });
* Until then, use hardcoded index.
*/
checkIfControllerPresent: function () {
var data = this.data;
// Once OpenVR / SteamVR return correct hand data in the supporting browsers, we can use hand property.
// var isPresent = this.checkControllerPresentAndSetup(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;
this.checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, { index: controllerIndex });
},

play: function () {
this.checkIfControllerPresent();
this.addControllersUpdateListener();
// 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();
// Note that due to gamepadconnected event propagation issues, we don't rely on events.
window.removeEventListener('gamepaddisconnected', this.checkIfControllerPresent, false);
this.checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, {index: controllerIndex});
},

injectTrackedControls: function () {
var el = this.el;
var data = this.data;
// handId: 0 - right, 1 - left, 2 - anything else...
var controller = data.hand === 'right' ? 0 : data.hand === 'left' ? 1 : 2;
// if we have an OpenVR Gamepad, use the fixed mapping
el.setAttribute('tracked-controls', {idPrefix: GAMEPAD_ID_PREFIX, controller: controller, rotationOffset: data.rotationOffset});

// If we have an OpenVR Gamepad, use the fixed mapping.
el.setAttribute('tracked-controls', {
idPrefix: GAMEPAD_ID_PREFIX,
// Hand IDs: 0 = right, 1 = left, 2 = anything else.
controller: data.hand === 'right' ? 0 : data.hand === 'left' ? 1 : 2,
rotationOffset: data.rotationOffset
});

// Load model.
if (!this.data.model) { return; }
this.el.setAttribute('obj-model', {
obj: VIVE_CONTROLLER_MODEL_OBJ_URL,
Expand All @@ -130,17 +141,23 @@ module.exports.Component = registerComponent('vive-controls', {
this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false);
},

onControllersUpdate: function () { this.checkIfControllerPresent(); },
onControllersUpdate: function () {
this.checkIfControllerPresent();
},

/**
* Rotate the trigger button based on how hard the trigger is pressed.
*/
onButtonChanged: function (evt) {
var button = this.mapping.buttons[evt.detail.id];
var buttonMeshes = this.buttonMeshes;
var analogValue;

if (!button) { return; }

if (button === 'trigger') {
analogValue = evt.detail.state.value;
// Update button mesh, if any.
// Update trigger rotation depending on button value.
if (buttonMeshes && buttonMeshes.trigger) {
buttonMeshes.trigger.rotation.x = -analogValue * (Math.PI / 12);
}
Expand All @@ -151,9 +168,13 @@ module.exports.Component = registerComponent('vive-controls', {
},

onModelLoaded: function (evt) {
var controllerObject3D = evt.detail.model;
var buttonMeshes;
var controllerObject3D = evt.detail.model;
var self = this;

if (!this.data.model) { return; }

// Store button meshes object to be able to change their colors.
buttonMeshes = this.buttonMeshes = {};
buttonMeshes.grip = {
left: controllerObject3D.getObjectByName('leftgrip'),
Expand All @@ -163,41 +184,53 @@ module.exports.Component = registerComponent('vive-controls', {
buttonMeshes.system = controllerObject3D.getObjectByName('systembutton');
buttonMeshes.trackpad = controllerObject3D.getObjectByName('touchpad');
buttonMeshes.trigger = controllerObject3D.getObjectByName('trigger');
// Offset pivot point

// Set default colors.
Object.keys(buttonMeshes).forEach(function (buttonName) {
self.setButtonColor(buttonName, self.data.buttonColor);
});

// Offset pivot point.
controllerObject3D.position.set(0, -0.015, 0.04);
},

onAxisMoved: function (evt) { this.emitIfAxesChanged(this, this.mapping.axes, evt); },
onAxisMoved: function (evt) {
this.emitIfAxesChanged(this, this.mapping.axes, evt);
},

onButtonEvent: function (id, evtName) {
var buttonName = this.mapping.buttons[id];
var color;
var i;

// Emit events.
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; }

// Update colors.
color = evtName === 'up' ? this.data.buttonColor : this.data.buttonHighlightColor;
if (Array.isArray(buttonName)) {
for (i = 0; i < buttonName.length; i++) {
this.updateButtonModel(buttonName[i], evtName);
this.setButtonColor(buttonName[i], color);
}
} else {
this.updateButtonModel(buttonName, evtName);
this.setButtonColor(buttonName, color);
}
},

updateButtonModel: function (buttonName, state) {
var color = state === 'up' ? this.data.buttonColor : this.data.buttonHighlightColor;
setButtonColor: function (buttonName, color) {
var buttonMeshes = this.buttonMeshes;

if (!buttonMeshes) { return; }

// Need to do both left and right sides for grip.
if (buttonName === 'grip') {
buttonMeshes.grip.left.material.color.set(color);
buttonMeshes.grip.right.material.color.set(color);
Expand Down
Loading