aframe
Version:
A web framework for building virtual reality experiences.
344 lines (303 loc) • 13.4 kB
JavaScript
var bind = require('../utils/bind');
var registerComponent = require('../core/component').registerComponent;
var THREE = require('../lib/three');
var trackedControlsUtils = require('../utils/tracked-controls');
var checkControllerPresentAndSetup = trackedControlsUtils.checkControllerPresentAndSetup;
var emitIfAxesChanged = trackedControlsUtils.emitIfAxesChanged;
var onButtonEvent = trackedControlsUtils.onButtonEvent;
var isWebXRAvailable = require('../utils/').device.isWebXRAvailable;
var GAMEPAD_ID_WEBXR = 'oculus-touch';
var GAMEPAD_ID_WEBVR = 'Oculus Touch';
// Prefix for Gen1 and Gen2 Oculus Touch Controllers.
var GAMEPAD_ID_PREFIX = isWebXRAvailable ? GAMEPAD_ID_WEBXR : GAMEPAD_ID_WEBVR;
// First generation model URL.
var TOUCH_CONTROLLER_MODEL_BASE_URL = 'https://cdn.aframe.io/controllers/oculus/oculus-touch-controller-';
var OCULUS_TOUCH_WEBVR = {
left: {
modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'left.gltf',
rayOrigin: {origin: {x: 0.008, y: -0.01, z: 0}, direction: {x: 0, y: -0.8, z: -1}},
modelPivotOffset: new THREE.Vector3(-0.005, 0.003, -0.055),
modelPivotRotation: new THREE.Euler(0, 0, 0)
},
right: {
modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'right.gltf',
rayOrigin: {origin: {x: -0.008, y: -0.01, z: 0}, direction: {x: 0, y: -0.8, z: -1}},
modelPivotOffset: new THREE.Vector3(0.005, 0.003, -0.055),
modelPivotRotation: new THREE.Euler(0, 0, 0)
}
};
var OCULUS_TOUCH_WEBXR = {
left: {
modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'left.gltf',
rayOrigin: {origin: {x: 0.002, y: -0.005, z: -0.03}, direction: {x: 0, y: -0.8, z: -1}},
modelPivotOffset: new THREE.Vector3(-0.005, 0.036, -0.037),
modelPivotRotation: new THREE.Euler(Math.PI / 4.5, 0, 0)
},
right: {
modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'right.gltf',
rayOrigin: {origin: {x: -0.002, y: -0.005, z: -0.03}, direction: {x: 0, y: -0.8, z: -1}},
modelPivotOffset: new THREE.Vector3(0.005, 0.036, -0.037),
modelPivotRotation: new THREE.Euler(Math.PI / 4.5, 0, 0)
}
};
var OCULUS_TOUCH_CONFIG = isWebXRAvailable ? OCULUS_TOUCH_WEBXR : OCULUS_TOUCH_WEBVR;
var CONTROLLER_DEFAULT = 'oculus-touch';
var CONTROLLER_PROPERTIES = {
'oculus-touch': OCULUS_TOUCH_CONFIG,
'oculus-touch-v2': {
left: {
modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'gen2-left.gltf',
rayOrigin: {origin: {x: -0.01, y: 0, z: -0.02}, direction: {x: 0, y: -0.5, z: -1}},
modelPivotOffset: new THREE.Vector3(0, 0, 0),
modelPivotRotation: new THREE.Euler(0, 0, 0)
},
right: {
modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'gen2-right.gltf',
rayOrigin: {origin: {x: 0.01, y: 0, z: -0.02}, direction: {x: 0, y: -0.5, z: -1}},
modelPivotOffset: new THREE.Vector3(0, 0, 0),
modelPivotRotation: new THREE.Euler(0, 0, 0)
}
},
'oculus-touch-v3': {
left: {
modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'v3-left.glb',
rayOrigin: {origin: {x: 0.015, y: 0.005, z: 0}, direction: {x: 0, y: 0, z: -1}},
modelPivotOffset: new THREE.Vector3(0.01, -0.01, 0.05),
modelPivotRotation: new THREE.Euler(Math.PI / 4, 0, 0)
},
right: {
modelUrl: TOUCH_CONTROLLER_MODEL_BASE_URL + 'v3-right.glb',
rayOrigin: {origin: {x: -0.015, y: 0.005, z: 0}, direction: {x: 0, y: 0, z: -1}},
modelPivotOffset: new THREE.Vector3(-0.01, -0.01, 0.05),
modelPivotRotation: new THREE.Euler(Math.PI / 4, 0, 0)
}
}
};
/**
* Button indices:
* 0 - thumbstick (which has separate axismove / thumbstickmoved events)
* 1 - trigger (with analog value, which goes up to 1)
* 2 - grip (with analog value, which goes up to 1)
* 3 - X (left) or A (right)
* 4 - Y (left) or B (right)
* 5 - surface (touch only)
*/
var INPUT_MAPPING_WEBVR = {
left: {
axes: {thumbstick: [0, 1]},
buttons: ['thumbstick', 'trigger', 'grip', 'xbutton', 'ybutton', 'surface']
},
right: {
axes: {thumbstick: [0, 1]},
buttons: ['thumbstick', 'trigger', 'grip', 'abutton', 'bbutton', 'surface']
}
};
/**
* Button indices:
* 0 - trigger
* 1 - grip
* 2 - none
* 3 - thumbstick
* 4 - X or A button
* 5 - Y or B button
* 6 - surface
*
* Axis:
* 0 - none
* 1 - none
* 2 - thumbstick
* 3 - thumbstick
* Reference: https://github.com/immersive-web/webxr-input-profiles/blob/master/packages/registry/profiles/oculus/oculus-touch.json
*/
var INPUT_MAPPING_WEBXR = {
left: {
axes: {thumbstick: [2, 3]},
buttons: ['trigger', 'grip', 'none', 'thumbstick', 'xbutton', 'ybutton', 'surface']
},
right: {
axes: {thumbstick: [2, 3]},
buttons: ['trigger', 'grip', 'none', 'thumbstick', 'abutton', 'bbutton', 'surface']
}
};
var INPUT_MAPPING = isWebXRAvailable ? INPUT_MAPPING_WEBXR : INPUT_MAPPING_WEBVR;
/**
* Oculus Touch controls.
* Interface with Oculus Touch controllers and map Gamepad events to
* controller buttons: thumbstick, trigger, grip, xbutton, ybutton, surface
* Load a controller model and highlight the pressed buttons.
*/
module.exports.Component = registerComponent('oculus-touch-controls', {
schema: {
hand: {default: 'left'},
buttonColor: {type: 'color', default: '#999'}, // Off-white.
buttonTouchColor: {type: 'color', default: '#8AB'},
buttonHighlightColor: {type: 'color', default: '#2DF'}, // Light blue.
model: {default: true},
controllerType: {default: 'auto', oneOf: ['auto', 'oculus-touch', 'oculus-touch-v2', 'oculus-touch-v3']},
orientationOffset: {type: 'vec3', default: {x: 43, y: 0, z: 0}}
},
mapping: INPUT_MAPPING,
bindMethods: function () {
this.onModelLoaded = bind(this.onModelLoaded, this);
this.onControllersUpdate = bind(this.onControllersUpdate, this);
this.checkIfControllerPresent = bind(this.checkIfControllerPresent, this);
this.onAxisMoved = bind(this.onAxisMoved, this);
},
init: function () {
var self = this;
this.onButtonChanged = bind(this.onButtonChanged, this);
this.onButtonDown = function (evt) { onButtonEvent(evt.detail.id, 'down', self, self.data.hand); };
this.onButtonUp = function (evt) { onButtonEvent(evt.detail.id, 'up', self, self.data.hand); };
this.onButtonTouchStart = function (evt) { onButtonEvent(evt.detail.id, 'touchstart', self, self.data.hand); };
this.onButtonTouchEnd = function (evt) { onButtonEvent(evt.detail.id, 'touchend', self, self.data.hand); };
this.controllerPresent = false;
this.lastControllerCheck = 0;
this.previousButtonValues = {};
this.rendererSystem = this.el.sceneEl.systems.renderer;
this.bindMethods();
},
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('axismove', this.onAxisMoved);
el.addEventListener('model-loaded', this.onModelLoaded);
this.controllerEventsActive = true;
},
removeEventListeners: function () {
var el = this.el;
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('axismove', this.onAxisMoved);
el.removeEventListener('model-loaded', this.onModelLoaded);
this.controllerEventsActive = false;
},
checkIfControllerPresent: function () {
checkControllerPresentAndSetup(this, GAMEPAD_ID_PREFIX, {
hand: this.data.hand
});
},
play: function () {
this.checkIfControllerPresent();
this.addControllersUpdateListener();
},
pause: function () {
this.removeEventListeners();
this.removeControllersUpdateListener();
},
loadModel: function (controller) {
var data = this.data;
var controllerId;
if (!data.model) { return; }
// Set the controller display model based on the data passed in.
this.displayModel = CONTROLLER_PROPERTIES[data.controllerType] || CONTROLLER_PROPERTIES[CONTROLLER_DEFAULT];
// If the developer is asking for auto-detection, see if the displayName can be retrieved to identify the specific unit.
// This only works for WebVR currently.
if (data.controllerType === 'auto') {
var trackedControlsSystem = this.el.sceneEl.systems['tracked-controls-webvr'];
// WebVR
if (trackedControlsSystem && trackedControlsSystem.vrDisplay) {
var displayName = trackedControlsSystem.vrDisplay.displayName;
// The Oculus Quest uses the updated generation 2 inside-out tracked controllers so update the displayModel.
if (/^Oculus Quest$/.test(displayName)) {
this.displayModel = CONTROLLER_PROPERTIES['oculus-touch-v2'];
}
} else { // WebXR
controllerId = CONTROLLER_DEFAULT;
controllerId = controller.profiles.indexOf('oculus-touch-v2') !== -1 ? 'oculus-touch-v2' : controllerId;
controllerId = controller.profiles.indexOf('oculus-touch-v3') !== -1 ? 'oculus-touch-v3' : controllerId;
this.displayModel = CONTROLLER_PROPERTIES[controllerId];
}
}
var modelUrl = this.displayModel[data.hand].modelUrl;
this.el.setAttribute('gltf-model', modelUrl);
},
injectTrackedControls: function (controller) {
var data = this.data;
var webXRId = GAMEPAD_ID_WEBXR;
var webVRId = data.hand === 'right' ? 'Oculus Touch (Right)' : 'Oculus Touch (Left)';
var id = isWebXRAvailable ? webXRId : webVRId;
this.el.setAttribute('tracked-controls', {
id: id,
hand: data.hand,
orientationOffset: data.orientationOffset,
handTrackingEnabled: false
});
this.loadModel(controller);
},
addControllersUpdateListener: function () {
this.el.sceneEl.addEventListener('controllersupdated', this.onControllersUpdate, false);
},
removeControllersUpdateListener: function () {
this.el.sceneEl.removeEventListener('controllersupdated', this.onControllersUpdate, false);
},
onControllersUpdate: function () {
// Note that due to gamepadconnected event propagation issues, we don't rely on events.
this.checkIfControllerPresent();
},
onButtonChanged: function (evt) {
var button = this.mapping[this.data.hand].buttons[evt.detail.id];
var buttonMeshes = this.buttonMeshes;
var analogValue;
if (!button) { return; }
if (button === 'trigger' || button === 'grip') { analogValue = evt.detail.state.value; }
// Update trigger and/or grip meshes, if any.
if (buttonMeshes) {
if (button === 'trigger' && buttonMeshes.trigger) {
buttonMeshes.trigger.rotation.x = this.originalXRotationTrigger - analogValue * (Math.PI / 26);
}
if (button === 'grip' && buttonMeshes.grip) {
buttonMeshes.grip.position.x = this.originalXPositionGrip + (this.data.hand === 'left' ? -1 : 1) * analogValue * 0.004;
}
}
// Pass along changed event with button state, using the buttom mapping for convenience.
this.el.emit(button + 'changed', evt.detail.state);
},
onModelLoaded: function (evt) {
var controllerObject3D = this.controllerObject3D = evt.detail.model;
var buttonMeshes;
if (!this.data.model) { return; }
buttonMeshes = this.buttonMeshes = {};
buttonMeshes.grip = controllerObject3D.getObjectByName('buttonHand');
this.originalXPositionGrip = buttonMeshes.grip && buttonMeshes.grip.position.x;
buttonMeshes.thumbstick = controllerObject3D.getObjectByName('stick');
buttonMeshes.trigger = controllerObject3D.getObjectByName('buttonTrigger');
this.originalXRotationTrigger = buttonMeshes.trigger && buttonMeshes.trigger.rotation.x;
buttonMeshes.xbutton = controllerObject3D.getObjectByName('buttonX');
buttonMeshes.abutton = controllerObject3D.getObjectByName('buttonA');
buttonMeshes.ybutton = controllerObject3D.getObjectByName('buttonY');
buttonMeshes.bbutton = controllerObject3D.getObjectByName('buttonB');
// Offset pivot point
controllerObject3D.position.copy(this.displayModel[this.data.hand].modelPivotOffset);
controllerObject3D.rotation.copy(this.displayModel[this.data.hand].modelPivotRotation);
this.el.emit('controllermodelready', {
name: 'oculus-touch-controls',
model: this.data.model,
rayOrigin: this.displayModel[this.data.hand].rayOrigin
});
},
onAxisMoved: function (evt) {
emitIfAxesChanged(this, this.mapping[this.data.hand].axes, evt);
},
updateModel: function (buttonName, evtName) {
if (!this.data.model) { return; }
this.updateButtonModel(buttonName, evtName);
},
updateButtonModel: function (buttonName, state) {
var button;
var color = (state === 'up' || state === 'touchend') ? this.data.buttonColor : state === 'touchstart' ? this.data.buttonTouchColor : this.data.buttonHighlightColor;
var buttonMeshes = this.buttonMeshes;
if (!this.data.model) { return; }
if (buttonMeshes && buttonMeshes[buttonName]) {
button = buttonMeshes[buttonName];
button.material.color.set(color);
this.rendererSystem.applyColorCorrection(button.material.color);
}
}
});