Skip to content

Commit efb5c3a

Browse files
kylebakeriodmarcos
andauthored
Quest 2 controller fixes (fixes issue #5607) (#5103)
* moved onButtonChanged to bindMethods; added onThumbstickMoved to support thumbstick model updates; added recycled euler for trigger updates, which are performed as a multi-dimensional rotation for v3 controller model; added thumbstickMoved event listener, and matching remove handler; slight comment cleanup in a few places; added onButtonChangedV3 and onModelLoadedV3 for handling non-conforming model cleanly while maintaining backwards compatibility unambiguously; added multiMeshFix to fix issue that all oculus controlers have had, where all buttons light up if any button is pressed; update to use model default color as button color when not being touched; * linting fixes * minor cleanup * diego request es5 changes * linter * add symmetrical comment to match the comment for onButtonChanged * requested fixes * most recent requested changes * adding removed condition * rename natural to original as requested * add a word to a comment * requested changes to comments * undo argument name correction as requested * Align comment with block * Change function name * Add missing comment Co-authored-by: Diego Marcos Segura <diego.marcos@gmail.com>
1 parent b42be85 commit efb5c3a

File tree

1 file changed

+177
-36
lines changed

1 file changed

+177
-36
lines changed

src/components/oculus-touch-controls.js

Lines changed: 177 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ module.exports.Component = registerComponent('oculus-touch-controls', {
153153
mapping: INPUT_MAPPING,
154154

155155
bindMethods: function () {
156+
this.onButtonChanged = bind(this.onButtonChanged, this);
157+
this.onThumbstickMoved = bind(this.onThumbstickMoved, this);
156158
this.onModelLoaded = bind(this.onModelLoaded, this);
157159
this.onControllersUpdate = bind(this.onControllersUpdate, this);
158160
this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this);
@@ -161,7 +163,6 @@ module.exports.Component = registerComponent('oculus-touch-controls', {
161163

162164
init: function () {
163165
var self = this;
164-
this.onButtonChanged = bind(this.onButtonChanged, this);
165166
this.onButtonDown = function (evt) { onButtonEvent(evt.detail.id, 'down', self, self.data.hand); };
166167
this.onButtonUp = function (evt) { onButtonEvent(evt.detail.id, 'up', self, self.data.hand); };
167168
this.onButtonTouchStart = function (evt) { onButtonEvent(evt.detail.id, 'touchstart', self, self.data.hand); };
@@ -171,6 +172,7 @@ module.exports.Component = registerComponent('oculus-touch-controls', {
171172
this.previousButtonValues = {};
172173
this.rendererSystem = this.el.sceneEl.systems.renderer;
173174
this.bindMethods();
175+
this.triggerEuler = new THREE.Euler();
174176
},
175177

176178
addEventListeners: function () {
@@ -182,6 +184,7 @@ module.exports.Component = registerComponent('oculus-touch-controls', {
182184
el.addEventListener('touchend', this.onButtonTouchEnd);
183185
el.addEventListener('axismove', this.onAxisMoved);
184186
el.addEventListener('model-loaded', this.onModelLoaded);
187+
el.addEventListener('thumbstickmoved', this.onThumbstickMoved);
185188
this.controllerEventsActive = true;
186189
},
187190

@@ -194,6 +197,7 @@ module.exports.Component = registerComponent('oculus-touch-controls', {
194197
el.removeEventListener('touchend', this.onButtonTouchEnd);
195198
el.removeEventListener('axismove', this.onAxisMoved);
196199
el.removeEventListener('model-loaded', this.onModelLoaded);
200+
el.removeEventListener('thumbstickmoved', this.onThumbstickMoved);
197201
this.controllerEventsActive = false;
198202
},
199203

@@ -220,14 +224,13 @@ module.exports.Component = registerComponent('oculus-touch-controls', {
220224
if (!data.model) { return; }
221225
// Set the controller display model based on the data passed in.
222226
this.displayModel = CONTROLLER_PROPERTIES[data.controllerType] || CONTROLLER_PROPERTIES[CONTROLLER_DEFAULT];
223-
// If the developer is asking for auto-detection, see if the displayName can be retrieved to identify the specific unit.
227+
// If the developer is asking for auto-detection, use the retrieved displayName to identify the specific unit.
224228
// This only works for WebVR currently.
225229
if (data.controllerType === 'auto') {
226230
var trackedControlsSystem = this.el.sceneEl.systems['tracked-controls-webvr'];
227231
// WebVR
228232
if (trackedControlsSystem && trackedControlsSystem.vrDisplay) {
229233
var displayName = trackedControlsSystem.vrDisplay.displayName;
230-
// The Oculus Quest uses the updated generation 2 inside-out tracked controllers so update the displayModel.
231234
if (/^Oculus Quest$/.test(displayName)) {
232235
this.displayModel = CONTROLLER_PROPERTIES['oculus-touch-v2'];
233236
}
@@ -239,6 +242,7 @@ module.exports.Component = registerComponent('oculus-touch-controls', {
239242
}
240243
}
241244
var modelUrl = this.displayModel[data.hand].modelUrl;
245+
this.isOculusTouchV3 = this.displayModel === CONTROLLER_PROPERTIES['oculus-touch-v3'];
242246
this.el.setAttribute('gltf-model', modelUrl);
243247
},
244248

@@ -270,48 +274,84 @@ module.exports.Component = registerComponent('oculus-touch-controls', {
270274
},
271275

272276
onButtonChanged: function (evt) {
277+
// move the button meshes
278+
if (this.isOculusTouchV3) {
279+
this.onButtonChangedV3(evt);
280+
} else {
281+
var button = this.mapping[this.data.hand].buttons[evt.detail.id];
282+
var buttonMeshes = this.buttonMeshes;
283+
var analogValue;
284+
if (!button) { return; }
285+
286+
if (button === 'trigger' || button === 'grip') { analogValue = evt.detail.state.value; }
287+
288+
if (buttonMeshes) {
289+
if (button === 'trigger' && buttonMeshes.trigger) {
290+
buttonMeshes.trigger.rotation.x = this.originalXRotationTrigger - analogValue * (Math.PI / 26);
291+
}
292+
if (button === 'grip' && buttonMeshes.grip) {
293+
analogValue *= this.data.hand === 'left' ? -1 : 1;
294+
buttonMeshes.grip.position.x = this.originalXPositionGrip + analogValue * 0.004;
295+
}
296+
}
297+
}
298+
// Pass along changed event with button state, using the buttom mapping for convenience.
299+
this.el.emit(button + 'changed', evt.detail.state);
300+
},
301+
302+
clickButtons: ['xbutton', 'ybutton', 'abutton', 'bbutton', 'thumbstick'],
303+
onButtonChangedV3: function (evt) {
273304
var button = this.mapping[this.data.hand].buttons[evt.detail.id];
274-
var buttonMeshes = this.buttonMeshes;
305+
var buttonObjects = this.buttonObjects;
275306
var analogValue;
276307
if (!button) { return; }
277308

278-
if (button === 'trigger' || button === 'grip') { analogValue = evt.detail.state.value; }
279-
280-
// Update trigger and/or grip meshes, if any.
281-
if (buttonMeshes) {
282-
if (button === 'trigger' && buttonMeshes.trigger) {
283-
buttonMeshes.trigger.rotation.x = this.originalXRotationTrigger - analogValue * (Math.PI / 26);
284-
}
285-
if (button === 'grip' && buttonMeshes.grip) {
286-
buttonMeshes.grip.position.x = this.originalXPositionGrip + (this.data.hand === 'left' ? -1 : 1) * analogValue * 0.004;
287-
}
309+
analogValue = evt.detail.state.value;
310+
analogValue *= this.data.hand === 'left' ? -1 : 1;
311+
312+
if (button === 'trigger') {
313+
this.triggerEuler.copy(this.buttonRanges.trigger.min.rotation);
314+
this.triggerEuler.x += analogValue * this.buttonRanges.trigger.diff.x;
315+
this.triggerEuler.y += analogValue * this.buttonRanges.trigger.diff.y;
316+
this.triggerEuler.z += analogValue * this.buttonRanges.trigger.diff.z;
317+
buttonObjects.trigger.setRotationFromEuler(this.triggerEuler);
318+
} else if (button === 'grip') {
319+
buttonObjects.grip.position.x = buttonObjects.grip.minX + analogValue * 0.004;
320+
} else if (this.clickButtons.includes(button)) {
321+
buttonObjects[button].position.y = analogValue === 0 ? this.buttonRanges[button].unpressedY : this.buttonRanges[button].pressedY;
288322
}
289-
290-
// Pass along changed event with button state, using the buttom mapping for convenience.
291-
this.el.emit(button + 'changed', evt.detail.state);
292323
},
293324

294325
onModelLoaded: function (evt) {
295-
var controllerObject3D = this.controllerObject3D = evt.detail.model;
296-
var buttonMeshes;
297-
298326
if (!this.data.model) { return; }
327+
if (this.isOculusTouchV3) {
328+
this.onOculusTouchV3ModelLoaded(evt);
329+
} else {
330+
// All oculus headset controller models prior to the Quest 2 (i.e., Oculus Touch V3)
331+
// used a consistent format that is handled here
332+
var controllerObject3D = this.controllerObject3D = evt.detail.model;
333+
var buttonMeshes;
334+
335+
buttonMeshes = this.buttonMeshes = {};
336+
337+
buttonMeshes.grip = controllerObject3D.getObjectByName('buttonHand');
338+
this.originalXPositionGrip = buttonMeshes.grip && buttonMeshes.grip.position.x;
339+
buttonMeshes.trigger = controllerObject3D.getObjectByName('buttonTrigger');
340+
this.originalXRotationTrigger = buttonMeshes.trigger && buttonMeshes.trigger.rotation.x;
341+
buttonMeshes.thumbstick = controllerObject3D.getObjectByName('stick');
342+
buttonMeshes.xbutton = controllerObject3D.getObjectByName('buttonX');
343+
buttonMeshes.abutton = controllerObject3D.getObjectByName('buttonA');
344+
buttonMeshes.ybutton = controllerObject3D.getObjectByName('buttonY');
345+
buttonMeshes.bbutton = controllerObject3D.getObjectByName('buttonB');
346+
}
299347

300-
buttonMeshes = this.buttonMeshes = {};
301-
302-
buttonMeshes.grip = controllerObject3D.getObjectByName('buttonHand');
303-
this.originalXPositionGrip = buttonMeshes.grip && buttonMeshes.grip.position.x;
304-
buttonMeshes.thumbstick = controllerObject3D.getObjectByName('stick');
305-
buttonMeshes.trigger = controllerObject3D.getObjectByName('buttonTrigger');
306-
this.originalXRotationTrigger = buttonMeshes.trigger && buttonMeshes.trigger.rotation.x;
307-
buttonMeshes.xbutton = controllerObject3D.getObjectByName('buttonX');
308-
buttonMeshes.abutton = controllerObject3D.getObjectByName('buttonA');
309-
buttonMeshes.ybutton = controllerObject3D.getObjectByName('buttonY');
310-
buttonMeshes.bbutton = controllerObject3D.getObjectByName('buttonB');
348+
for (var button in this.buttonMeshes) {
349+
if (this.buttonMeshes[button]) {
350+
cloneMeshMaterial(this.buttonMeshes[button]);
351+
}
352+
}
311353

312-
// Offset pivot point
313-
controllerObject3D.position.copy(this.displayModel[this.data.hand].modelPivotOffset);
314-
controllerObject3D.rotation.copy(this.displayModel[this.data.hand].modelPivotRotation);
354+
this.applyOffset(evt.detail.model);
315355

316356
this.el.emit('controllermodelready', {
317357
name: 'oculus-touch-controls',
@@ -320,24 +360,125 @@ module.exports.Component = registerComponent('oculus-touch-controls', {
320360
});
321361
},
322362

363+
applyOffset: function (model) {
364+
model.position.copy(this.displayModel[this.data.hand].modelPivotOffset);
365+
model.rotation.copy(this.displayModel[this.data.hand].modelPivotRotation);
366+
},
367+
368+
onOculusTouchV3ModelLoaded: function (evt) {
369+
var controllerObject3D = this.controllerObject3D = evt.detail.model;
370+
371+
var buttonObjects = this.buttonObjects = {};
372+
var buttonMeshes = this.buttonMeshes = {};
373+
var buttonRanges = this.buttonRanges = {};
374+
375+
buttonMeshes.grip = controllerObject3D.getObjectByName('squeeze');
376+
buttonObjects.grip = controllerObject3D.getObjectByName('xr_standard_squeeze_pressed_value');
377+
buttonRanges.grip = {
378+
min: controllerObject3D.getObjectByName('xr_standard_squeeze_pressed_min'),
379+
max: controllerObject3D.getObjectByName('xr_standard_squeeze_pressed_max')
380+
};
381+
buttonObjects.grip.minX = buttonObjects.grip.position.x;
382+
383+
buttonMeshes.thumbstick = controllerObject3D.getObjectByName('thumbstick');
384+
buttonObjects.thumbstick = controllerObject3D.getObjectByName('xr_standard_thumbstick_pressed_value');
385+
buttonRanges.thumbstick = {
386+
min: controllerObject3D.getObjectByName('xr_standard_thumbstick_pressed_min'),
387+
max: controllerObject3D.getObjectByName('xr_standard_thumbstick_pressed_max'),
388+
originalRotation: this.buttonObjects.thumbstick.rotation.clone()
389+
};
390+
buttonRanges.thumbstick.pressedY = buttonObjects.thumbstick.position.y;
391+
buttonRanges.thumbstick.unpressedY =
392+
buttonRanges.thumbstick.pressedY + Math.abs(buttonRanges.thumbstick.max.position.y) - Math.abs(buttonRanges.thumbstick.min.position.y);
393+
394+
buttonMeshes.trigger = controllerObject3D.getObjectByName('trigger');
395+
buttonObjects.trigger = controllerObject3D.getObjectByName('xr_standard_trigger_pressed_value');
396+
buttonRanges.trigger = {
397+
min: controllerObject3D.getObjectByName('xr_standard_trigger_pressed_min'),
398+
max: controllerObject3D.getObjectByName('xr_standard_trigger_pressed_max')
399+
};
400+
buttonRanges.trigger.diff = {
401+
x: Math.abs(buttonRanges.trigger.max.rotation.x) - Math.abs(buttonRanges.trigger.min.rotation.x),
402+
y: Math.abs(buttonRanges.trigger.max.rotation.y) - Math.abs(buttonRanges.trigger.min.rotation.y),
403+
z: Math.abs(buttonRanges.trigger.max.rotation.z) - Math.abs(buttonRanges.trigger.min.rotation.z)
404+
};
405+
406+
var button1 = this.data.hand === 'left' ? 'x' : 'a';
407+
var button2 = this.data.hand === 'left' ? 'y' : 'b';
408+
var button1id = button1 + 'button';
409+
var button2id = button2 + 'button';
410+
411+
buttonMeshes[button1id] = controllerObject3D.getObjectByName(button1 + '_button');
412+
buttonObjects[button1id] = controllerObject3D.getObjectByName(button1 + '_button_pressed_value');
413+
buttonRanges[button1id] = {
414+
min: controllerObject3D.getObjectByName(button1 + '_button_pressed_min'),
415+
max: controllerObject3D.getObjectByName(button1 + '_button_pressed_max')
416+
};
417+
418+
buttonMeshes[button2id] = controllerObject3D.getObjectByName(button2 + '_button');
419+
buttonObjects[button2id] = controllerObject3D.getObjectByName(button2 + '_button_pressed_value');
420+
buttonRanges[button2id] = {
421+
min: controllerObject3D.getObjectByName(button2 + '_button_pressed_min'),
422+
max: controllerObject3D.getObjectByName(button2 + '_button_pressed_max')
423+
};
424+
425+
buttonRanges[button1id].unpressedY = buttonObjects[button1id].position.y;
426+
buttonRanges[button1id].pressedY =
427+
buttonRanges[button1id].unpressedY + Math.abs(buttonRanges[button1id].max.position.y) - Math.abs(buttonRanges[button1id].min.position.y);
428+
429+
buttonRanges[button2id].unpressedY = buttonObjects[button2id].position.y;
430+
buttonRanges[button2id].pressedY =
431+
buttonRanges[button2id].unpressedY - Math.abs(buttonRanges[button2id].max.position.y) + Math.abs(buttonRanges[button2id].min.position.y);
432+
},
433+
323434
onAxisMoved: function (evt) {
324435
emitIfAxesChanged(this, this.mapping[this.data.hand].axes, evt);
325436
},
326437

438+
onThumbstickMoved: function (evt) {
439+
if (!this.isOculusTouchV3 || !this.buttonMeshes || !this.buttonMeshes.thumbstick) { return; }
440+
for (var axis in evt.detail) {
441+
this.buttonObjects.thumbstick.rotation[this.axisMap[axis]] =
442+
this.buttonRanges.thumbstick.originalRotation[this.axisMap[axis]] -
443+
(Math.PI / 8) *
444+
evt.detail[axis] *
445+
(axis === 'y' || this.data.hand === 'right' ? -1 : 1);
446+
}
447+
},
448+
axisMap: {
449+
y: 'x',
450+
x: 'z'
451+
},
452+
327453
updateModel: function (buttonName, evtName) {
328454
if (!this.data.model) { return; }
329455
this.updateButtonModel(buttonName, evtName);
330456
},
331457

332458
updateButtonModel: function (buttonName, state) {
459+
// update the button mesh colors
333460
var button;
334-
var color = (state === 'up' || state === 'touchend') ? this.data.buttonColor : state === 'touchstart' ? this.data.buttonTouchColor : this.data.buttonHighlightColor;
461+
var color = (state === 'up' || state === 'touchend') ? this.buttonMeshes[buttonName].originalColor || this.data.buttonColor : state === 'touchstart' ? this.data.buttonTouchColor : this.data.buttonHighlightColor;
335462
var buttonMeshes = this.buttonMeshes;
336-
if (!this.data.model) { return; }
463+
337464
if (buttonMeshes && buttonMeshes[buttonName]) {
338465
button = buttonMeshes[buttonName];
339466
button.material.color.set(color);
340467
this.rendererSystem.applyColorCorrection(button.material.color);
341468
}
342469
}
343470
});
471+
472+
/**
473+
* Some of the controller models share the same material for different parts (buttons, triggers...).
474+
* In order to change their color independently we have to create separate materials.
475+
*/
476+
function cloneMeshMaterial (object3d) {
477+
object3d.traverse(function (node) {
478+
if (node.type !== 'Mesh') return;
479+
let newMaterial = node.material.clone();
480+
object3d.originalColor = node.material.color;
481+
node.material.dispose();
482+
node.material = newMaterial;
483+
});
484+
}

0 commit comments

Comments
 (0)