Skip to content

Commit cb1399e

Browse files
machenmusikdmarcos
authored andcommitted
3DOF support in tracked-controls, daydream-controls, head/arm model (#2538)
* add daydream-controls, using new 3DOF support in tracked-controls * add support for daydream-controls, using singlehand to determine which hand gets it * apply degenerate arm model when not 6DOF controller * if gamepad pose has no orientation, use camera * updated docs * add headElement to schema, so non-6DOF model can apply when head is not the active camera e.g. spectator view * remove arm model constants from schema; use default user height constant; hand => defaultHand * fix test case where controller was undefined * make default hand a constant, per discussion on PR * DEFAULT_HAND => DEFAULT_HANDEDNESS; daydream-controls should provide hand, so use it to filter, and attach to both hand-controls * move arm model into separate function
1 parent 29d3e13 commit cb1399e

File tree

8 files changed

+357
-24
lines changed

8 files changed

+357
-24
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
title: daydream-controls
3+
type: components
4+
layout: docs
5+
parent_section: components
6+
---
7+
8+
[trackedcontrols]: ./tracked-controls.md
9+
10+
The daydream-controls component interfaces with the Google Daydream controllers.
11+
It wraps the [tracked-controls component][trackedcontrols] while adding button
12+
mappings, events, and a Daydream controller model that highlights the touched
13+
and/or pressed buttons (trackpad).
14+
15+
## Example
16+
17+
```html
18+
<a-entity daydream-controls="hand: left"></a-entity>
19+
<a-entity daydream-controls="hand: right"></a-entity>
20+
```
21+
22+
## Value
23+
24+
| Property | Description | Default |
25+
|----------------------|----------------------------------------------------|---------|
26+
| buttonColor | Button colors when not pressed. | #000000 |
27+
| buttonTouchedColor | Button colors when touched. | #777777 |
28+
| buttonHighlightColor | Button colors when pressed and active. | #FFFFFF |
29+
| hand | The hand that will be tracked (i.e., right, left). | right |
30+
| model | Whether the Daydream controller model is loaded. | true |
31+
| rotationOffset | Offset to apply to model rotation. | 0 |
32+
33+
## Events
34+
35+
| Event Name | Description |
36+
| ---------- | ----------- |
37+
| trackpaddown | Trackpad pressed. |
38+
| trackpadup | Trackpad released. |
39+
| trackpadtouchstart | Trackpad touched. |
40+
| trackpadtouchend | Trackpad not touched. |
41+
42+
## Assets
43+
44+
- [Controller OBJ](https://cdn.aframe.io/controllers/google/vr_controller_daydream.obj)
45+
- [Controller MTL](https://cdn.aframe.io/controllers/google/vr_controller_daydream.mtl)
46+

‎docs/components/hand-controls.md‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@ parent_section: components
88
[tracked]: ./tracked-controls.md
99
[vive]: ./vive-controls.md
1010
[oculustouch]: ./oculus-touch-controls.md
11+
[daydream]: ./daydream-controls.md
1112

1213
The hand-controls gives tracked hands (using a prescribed model) with animated
13-
gestures. hand-controls wraps the [vive-controls][vive] and
14-
[oculus-touch-controls][oculustouch] components, which in turn wrap the
14+
gestures. hand-controls wraps the [vive-controls][vive], [oculus-touch-controls][oculustouch],
15+
and [daydream-controls][daydream] components, which in turn wrap the
1516
[tracked-controls component][tracked]. The component gives extra events and
1617
handles hand animations and poses.
1718

‎docs/components/tracked-controls.md‎

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@ parent_section: components
88
[handcontrols]: ./hand-controls.md
99
[oculustouchcontrols]: ./oculus-touch-controls.md
1010
[vivecontrols]: ./vive-controls.md
11+
[daydreamcontrols]: ./daydream-controls.md
1112

1213
The tracked-controls component interfaces with tracked controllers.
1314
tracked-controls uses the Gamepad API to handle tracked controllers, and is
1415
abstracted by the [hand-controls component][handcontrols] as well as the
15-
[vive-controls][vivecontrols] and [oculus-touch-controls][oculustouchcontrols]
16+
[vive-controls][vivecontrols], [oculus-touch-controls][oculustouchcontrols], and
17+
[daydream-controls][daydreamcontrols]
1618
components. This component elects the appropriate controller, applies pose to
17-
the entity, observes buttons state and emits appropriate events.
19+
the entity, observes buttons state and emits appropriate events. For non-6DOF controllers
20+
such as [daydream-controls][daydreamcontrols], a primitive arm model is used to emulate
21+
positional data.
1822

1923
## Example
2024

@@ -28,11 +32,17 @@ so using idPrefix for Vive / OpenVR controllers is recommended.
2832

2933
## Value
3034

31-
| Property | Description | Default Value |
32-
|-------------|-------------------------------------------------------------- --|---------------|
33-
| controller | Index of the controller in array returned by the Gamepad API. | 0 |
34-
| id | Selects the controller from the Gamepad API using exact match. | |
35-
| idPrefix | Selects the controller from the Gamepad API using prefix match. | |
35+
| Property | Description | Default Value |
36+
|-------------------|-------------------------------------------------------------- --|----------------------------|
37+
| controller | Index of the controller in array returned by the Gamepad API. | 0 |
38+
| id | Selects the controller from the Gamepad API using exact match. | |
39+
| idPrefix | Selects the controller from the Gamepad API using prefix match. | |
40+
| rotationOffset | Offset to add to model rotation. | 0 |
41+
| headElement | Head element for arm model if needed (if not active camera). | |
42+
| hand | Which hand to use, if arm model is needed. (left negates X) | right |
43+
| eyesToElbow | Arm model vector from eyes to elbow as user height ratio. | {x:0.175, y:-0.3, z:-0.03} |
44+
| forearm | Arm model vector for forearm as user height ratio. | {x:0, y:0, z:-0.175} |
45+
| defaultUserHeight | Default user height (for cameras with zero). | 1.6 |
3646

3747
## Events
3848

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
var registerComponent = require('../core/component').registerComponent;
2+
var bind = require('../utils/bind');
3+
var isControllerPresent = require('../utils/tracked-controls').isControllerPresent;
4+
5+
var DEFAULT_HANDEDNESS = require('../constants').DEFAULT_HANDEDNESS;
6+
7+
var DAYDREAM_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/google/';
8+
var DAYDREAM_CONTROLLER_MODEL_OBJ_URL = DAYDREAM_CONTROLLER_MODEL_BASE_URL + 'vr_controller_daydream.obj';
9+
var DAYDREAM_CONTROLLER_MODEL_OBJ_MTL = DAYDREAM_CONTROLLER_MODEL_BASE_URL + 'vr_controller_daydream.mtl';
10+
11+
var GAMEPAD_ID_PREFIX = 'Daydream Controller';
12+
13+
/**
14+
* Vive Controls Component
15+
* Interfaces with vive controllers and maps Gamepad events to
16+
* common controller buttons: trackpad, trigger, grip, menu and system
17+
* It loads a controller model and highlights the pressed buttons
18+
*/
19+
module.exports.Component = registerComponent('daydream-controls', {
20+
schema: {
21+
hand: {default: DEFAULT_HANDEDNESS}, // This informs the degenerate arm model.
22+
buttonColor: {type: 'color', default: '#000000'},
23+
buttonTouchedColor: {type: 'color', default: '#777777'},
24+
buttonHighlightColor: {type: 'color', default: '#FFFFFF'},
25+
model: {default: true},
26+
rotationOffset: {default: 0} // use -999 as sentinel value to auto-determine based on hand
27+
},
28+
29+
// buttonId
30+
// 0 - trackpad
31+
// 1 - menu ( never dispatched on this layer )
32+
// 2 - system ( never dispatched on this layer )
33+
mapping: {
34+
axis0: 'trackpad',
35+
axis1: 'trackpad',
36+
button0: 'trackpad',
37+
button1: 'menu',
38+
button2: 'system'
39+
},
40+
41+
bindMethods: function () {
42+
this.onModelLoaded = bind(this.onModelLoaded, this);
43+
this.onControllersUpdate = bind(this.onControllersUpdate, this);
44+
this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this);
45+
this.removeControllersUpdateListener = bind(this.removeControllersUpdateListener, this);
46+
this.onGamepadConnected = bind(this.onGamepadConnected, this);
47+
this.onGamepadDisconnected = bind(this.onGamepadDisconnected, this);
48+
},
49+
50+
init: function () {
51+
var self = this;
52+
this.animationActive = 'pointing';
53+
this.onButtonDown = function (evt) { self.onButtonEvent(evt.detail.id, 'down'); };
54+
this.onButtonUp = function (evt) { self.onButtonEvent(evt.detail.id, 'up'); };
55+
this.onButtonTouchStart = function (evt) { self.onButtonEvent(evt.detail.id, 'touchstart'); };
56+
this.onButtonTouchEnd = function (evt) { self.onButtonEvent(evt.detail.id, 'touchend'); };
57+
this.onAxisMoved = bind(this.onAxisMoved, this);
58+
this.controllerPresent = false;
59+
this.everGotGamepadEvent = false;
60+
this.lastControllerCheck = 0;
61+
this.bindMethods();
62+
this.isControllerPresent = isControllerPresent; // to allow mock
63+
},
64+
65+
addEventListeners: function () {
66+
var el = this.el;
67+
el.addEventListener('buttondown', this.onButtonDown);
68+
el.addEventListener('buttonup', this.onButtonUp);
69+
el.addEventListener('touchstart', this.onButtonTouchStart);
70+
el.addEventListener('touchend', this.onButtonTouchEnd);
71+
el.addEventListener('model-loaded', this.onModelLoaded);
72+
el.addEventListener('axismove', this.onAxisMoved);
73+
},
74+
75+
removeEventListeners: function () {
76+
var el = this.el;
77+
el.removeEventListener('buttondown', this.onButtonDown);
78+
el.removeEventListener('buttonup', this.onButtonUp);
79+
el.removeEventListener('touchstart', this.onButtonTouchStart);
80+
el.removeEventListener('touchend', this.onButtonTouchEnd);
81+
el.removeEventListener('model-loaded', this.onModelLoaded);
82+
el.removeEventListener('axismove', this.onAxisMoved);
83+
},
84+
85+
checkIfControllerPresent: function () {
86+
var isPresent = this.isControllerPresent(this.el.sceneEl, GAMEPAD_ID_PREFIX, {hand: this.data.hand});
87+
if (isPresent === this.controllerPresent) { return; }
88+
this.controllerPresent = isPresent;
89+
if (isPresent) { this.injectTrackedControls(); } // inject track-controls
90+
},
91+
92+
onGamepadConnected: function (evt) {
93+
// for now, don't disable controller update listening, due to
94+
// apparent issue with FF Nightly only sending one event and seeing one controller;
95+
// this.everGotGamepadEvent = true;
96+
// this.removeControllersUpdateListener();
97+
this.checkIfControllerPresent();
98+
},
99+
100+
onGamepadDisconnected: function (evt) {
101+
// for now, don't disable controller update listening, due to
102+
// apparent issue with FF Nightly only sending one event and seeing one controller;
103+
// this.everGotGamepadEvent = true;
104+
// this.removeControllersUpdateListener();
105+
this.checkIfControllerPresent();
106+
},
107+
108+
play: function () {
109+
this.checkIfControllerPresent();
110+
window.addEventListener('gamepadconnected', this.onGamepadConnected, false);
111+
window.addEventListener('gamepaddisconnected', this.onGamepadDisconnected, false);
112+
this.addControllersUpdateListener();
113+
this.addEventListeners();
114+
},
115+
116+
pause: function () {
117+
window.removeEventListener('gamepadconnected', this.onGamepadConnected, false);
118+
window.removeEventListener('gamepaddisconnected', this.onGamepadDisconnected, false);
119+
this.removeControllersUpdateListener();
120+
this.removeEventListeners();
121+
},
122+
123+
injectTrackedControls: function () {
124+
var el = this.el;
125+
var data = this.data;
126+
el.setAttribute('tracked-controls', {idPrefix: GAMEPAD_ID_PREFIX, hand: data.hand, rotationOffset: data.rotationOffset});
127+
if (!this.data.model) { return; }
128+
this.el.setAttribute('obj-model', {
129+
obj: DAYDREAM_CONTROLLER_MODEL_OBJ_URL,
130+
mtl: DAYDREAM_CONTROLLER_MODEL_OBJ_MTL
131+
});
132+
},
133+
134+
addControllersUpdateListener: function () {
135+
this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false);
136+
},
137+
138+
removeControllersUpdateListener: function () {
139+
this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false);
140+
},
141+
142+
onControllersUpdate: function () {
143+
if (!this.everGotGamepadEvent) { this.checkIfControllerPresent(); }
144+
},
145+
146+
// No need for onButtonChanged, since Daydream controller has no analog buttons.
147+
148+
onModelLoaded: function (evt) {
149+
var controllerObject3D = evt.detail.model;
150+
var buttonMeshes;
151+
if (!this.data.model) { return; }
152+
buttonMeshes = this.buttonMeshes = {};
153+
buttonMeshes.menu = controllerObject3D.getObjectByName('AppButton_AppButton_Cylinder.004');
154+
buttonMeshes.system = controllerObject3D.getObjectByName('HomeButton_HomeButton_Cylinder.005');
155+
buttonMeshes.trackpad = controllerObject3D.getObjectByName('TouchPad_TouchPad_Cylinder.003');
156+
// Offset pivot point
157+
controllerObject3D.position.set(0, 0, -0.04);
158+
},
159+
160+
onAxisMoved: function (evt) {
161+
if (evt.detail.axis[0] === 0 && evt.detail.axis[1] === 0) { return; }
162+
this.el.emit('trackpadmoved', { x: evt.detail.axis[0], y: evt.detail.axis[1] });
163+
},
164+
165+
onButtonEvent: function (id, evtName) {
166+
var buttonName = this.mapping['button' + id];
167+
var i;
168+
if (Array.isArray(buttonName)) {
169+
for (i = 0; i < buttonName.length; i++) {
170+
this.el.emit(buttonName[i] + evtName);
171+
}
172+
} else {
173+
this.el.emit(buttonName + evtName);
174+
}
175+
this.updateModel(buttonName, evtName);
176+
},
177+
178+
updateModel: function (buttonName, evtName) {
179+
var i;
180+
if (!this.data.model) { return; }
181+
if (Array.isArray(buttonName)) {
182+
for (i = 0; i < buttonName.length; i++) {
183+
this.updateButtonModel(buttonName[i], evtName);
184+
}
185+
} else {
186+
this.updateButtonModel(buttonName, evtName);
187+
}
188+
},
189+
190+
updateButtonModel: function (buttonName, state) {
191+
var buttonMeshes = this.buttonMeshes;
192+
if (!buttonMeshes || !buttonMeshes[buttonName]) { return; }
193+
var color;
194+
switch (state) {
195+
case 'down':
196+
color = this.data.buttonHighlightColor;
197+
break;
198+
case 'touchstart':
199+
color = this.data.buttonTouchedColor;
200+
break;
201+
default:
202+
color = this.data.buttonColor;
203+
}
204+
buttonMeshes[buttonName].material.color.set(color);
205+
}
206+
});

‎src/components/hand-controls.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ module.exports.Component = registerComponent('hand-controls', {
124124
}
125125
el.setAttribute('vive-controls', controlConfiguration);
126126
el.setAttribute('oculus-touch-controls', controlConfiguration);
127+
el.setAttribute('daydream-controls', controlConfiguration);
127128
el.setAttribute('blend-character-model', modelUrl);
128129
},
129130

‎src/components/index.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ require('./blend-character-model');
22
require('./camera');
33
require('./collada-model');
44
require('./cursor');
5+
require('./daydream-controls');
56
require('./geometry');
67
require('./gltf-model');
78
require('./hand-controls');

0 commit comments

Comments
 (0)