From 061854b9ebb9f2f71dcfa4526ab54e220c7b0a09 Mon Sep 17 00:00:00 2001 From: machenmusik Date: Fri, 31 Mar 2017 02:27:22 -0400 Subject: [PATCH] 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 5713d64c28f..c33a38c990d 100644 --- a/src/components/hand-controls.js +++ b/src/components/hand-controls.js @@ -125,6 +125,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); el.setAttribute('blend-character-model', modelUrl); }, 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');