Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support GearVR 3DOF controller #2545

Merged
merged 4 commits into from
Apr 14, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions docs/components/gearvr-controls.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
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
<!-- Match Gear VR controller if present, regardless of hand. -->
<a-entity gearvr-controls></a-entity>

<!-- Match Gear VR controller if present and for specified hand. -->
<a-entity gearvr-controls="hand: left"></a-entity>
<a-entity gearvr-controls="hand: right"></a-entity>
```

## 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 (e.g., right, left). | |
| 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)

199 changes: 199 additions & 0 deletions src/components/gearvr-controls.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
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/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';

/**
* 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: ''}, // 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: {
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, but you haven't merged it yet! from 5 days ago:

rebased, but will need changes when #2513 is merged

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, this.data.hand ? {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;
Copy link
Member

Choose a reason for hiding this comment

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

Don't we want to set this flag?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

assuming that #2513 survives with that flag, that is one of the changes to bring in line with #2513

// this.removeControllersUpdateListener();
this.checkIfControllerPresent();
},

onGamepadDisconnected: function (evt) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this method needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

assuming that #2513 survives with that flag, that is one of the changes to bring in line with #2513

// 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, 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('Trigger');
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 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);
}
});
1 change: 1 addition & 0 deletions src/components/hand-controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down
1 change: 1 addition & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down