full vr gamepads support! also make it possible to play with keyboard / external gamepad
This commit is contained in:
parent
c36d42e5d5
commit
c46ba02ca1
2 changed files with 96 additions and 10 deletions
|
|
@ -582,6 +582,7 @@ async function connect (connectOptions: {
|
|||
|
||||
function changeCallback () {
|
||||
notification.show = false
|
||||
if (renderer.xr.isPresenting) return // todo
|
||||
if (!pointerLock.hasPointerLock && activeModalStack.length === 0) {
|
||||
showModal(pauseMenu)
|
||||
}
|
||||
|
|
|
|||
105
src/vr.js
105
src/vr.js
|
|
@ -1,6 +1,8 @@
|
|||
const { VRButton } = require('three/examples/jsm/webxr/VRButton.js')
|
||||
const { GLTFLoader } = require('three/examples/jsm/loaders/GLTFLoader.js')
|
||||
const { XRControllerModelFactory } = require('three/examples/jsm/webxr/XRControllerModelFactory.js')
|
||||
const { buttonMap: standardButtonsMap } = require('contro-max/build/gamepad')
|
||||
const { activeModalStack, hideModal } = require('./globalState')
|
||||
|
||||
async function initVR () {
|
||||
const { renderer } = viewer
|
||||
|
|
@ -19,19 +21,60 @@ async function initVR () {
|
|||
const controllerModelFactory = new XRControllerModelFactory(new GLTFLoader())
|
||||
const controller1 = renderer.xr.getControllerGrip(0)
|
||||
const controller2 = renderer.xr.getControllerGrip(1)
|
||||
|
||||
// todo the logic written here can be hard to understand as it was designed to work in gamepad api emulation mode, will be refactored once there is a contro-max rewrite is done
|
||||
const virtualGamepadIndex = 4
|
||||
let connectedVirtualGamepad
|
||||
const manageXrInputSource = ({ gamepad, handedness = defaultHandedness }, defaultHandedness, removeAction = false) => {
|
||||
if (handedness === 'right') {
|
||||
const event = new Event(removeAction ? 'gamepaddisconnected' : 'gamepadconnected') // todo need to expose and use external gamepads api in contro-max instead
|
||||
event.gamepad = removeAction ? connectedVirtualGamepad : { ...gamepad, mapping: 'standard', index: virtualGamepadIndex }
|
||||
connectedVirtualGamepad = event.gamepad
|
||||
window.dispatchEvent(event)
|
||||
}
|
||||
}
|
||||
let hand1 = controllerModelFactory.createControllerModel(controller1)
|
||||
controller1.addEventListener('connected', (event) => {
|
||||
hand1.xrInputSource = event.data
|
||||
manageXrInputSource(event.data, 'left')
|
||||
user.add(controller1)
|
||||
})
|
||||
controller1.add(hand1)
|
||||
let hand2 = controllerModelFactory.createControllerModel(controller2)
|
||||
controller2.addEventListener('connected', (event) => {
|
||||
hand2.xrInputSource = event.data
|
||||
manageXrInputSource(event.data, 'right')
|
||||
user.add(controller2)
|
||||
})
|
||||
controller2.add(hand2)
|
||||
|
||||
controller1.addEventListener('disconnected', () => {
|
||||
// don't handle removal of gamepads for now as is don't affect contro-max
|
||||
hand1.xrInputSource = undefined
|
||||
manageXrInputSource(hand1.xrInputSource, 'left', true)
|
||||
})
|
||||
controller2.addEventListener('disconnected', () => {
|
||||
hand2.xrInputSource = undefined
|
||||
manageXrInputSource(hand1.xrInputSource, 'right', true)
|
||||
})
|
||||
|
||||
const originalGetGamepads = navigator.getGamepads.bind(navigator)
|
||||
navigator.getGamepads = () => {
|
||||
const originalGamepads = originalGetGamepads()
|
||||
if (!hand1.xrInputSource || !hand2.xrInputSource) return originalGamepads
|
||||
return [
|
||||
...originalGamepads,
|
||||
{
|
||||
axes: remapAxes(hand2.xrInputSource.gamepad.axes, hand1.xrInputSource.gamepad.axes),
|
||||
buttons: remapButtons(hand2.xrInputSource.gamepad.buttons, hand1.xrInputSource.gamepad.buttons),
|
||||
connected: true,
|
||||
mapping: 'standard',
|
||||
id: '',
|
||||
index: virtualGamepadIndex
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
let rotSnapReset = true
|
||||
let yawOffset = 0
|
||||
renderer.setAnimationLoop(() => {
|
||||
|
|
@ -41,6 +84,7 @@ async function initVR () {
|
|||
hand1.yAxis = hand1.xrInputSource.gamepad.axes[3]
|
||||
hand2.xAxis = hand2.xrInputSource.gamepad.axes[2]
|
||||
hand2.yAxis = hand2.xrInputSource.gamepad.axes[3]
|
||||
// hand2 should be right
|
||||
if (hand1.xrInputSource.handedness === 'right') {
|
||||
const tmp = hand2
|
||||
hand2 = hand1
|
||||
|
|
@ -57,28 +101,69 @@ async function initVR () {
|
|||
rotSnapReset = true
|
||||
}
|
||||
|
||||
viewer.setFirstPersonCamera(null, yawOffset, 0)
|
||||
// viewer.setFirstPersonCamera(null, yawOffset, 0)
|
||||
viewer.setFirstPersonCamera(null, bot.entity.yaw, bot.entity.pitch)
|
||||
|
||||
const xrCamera = renderer.xr.getCamera(viewer.camera)
|
||||
const d = xrCamera.getWorldDirection()
|
||||
bot.entity.yaw = Math.atan2(-d.x, -d.z)
|
||||
bot.entity.pitch = Math.asin(d.y)
|
||||
// todo restore this logic (need to preserve ability to move camera)
|
||||
// const xrCamera = renderer.xr.getCamera(viewer.camera)
|
||||
// const d = xrCamera.getWorldDirection() // todo target
|
||||
// bot.entity.yaw = Math.atan2(-d.x, -d.z)
|
||||
// bot.entity.pitch = Math.asin(d.y)
|
||||
|
||||
bot.physics.stepHeight = 1
|
||||
bot.setControlState('forward', hand2.yAxis < -0.5)
|
||||
bot.setControlState('back', hand2.yAxis > 0.5)
|
||||
bot.setControlState('right', hand2.xAxis < -0.5)
|
||||
bot.setControlState('left', hand2.xAxis > 0.5)
|
||||
|
||||
viewer.update()
|
||||
viewer.render()
|
||||
})
|
||||
renderer.xr.addEventListener('sessionstart', () => {
|
||||
viewer.cameraObjectOverride = user
|
||||
// close all modals to be in game
|
||||
for (const _modal of activeModalStack) {
|
||||
hideModal(undefined, {}, { force: true })
|
||||
}
|
||||
})
|
||||
renderer.xr.addEventListener('sessionend', () => {
|
||||
viewer.cameraObjectOverride = undefined
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { initVR }
|
||||
module.exports.initVR = initVR
|
||||
|
||||
const xrStandardRightButtonsMap = [
|
||||
[0 /* trigger */, 'Right Trigger'],
|
||||
[1 /* squeeze */, 'Right Bumper'],
|
||||
// need to think of a way to support touchpad input
|
||||
[3 /* Thumbstick Press */, 'Right Stick'],
|
||||
[4 /* A */, 'A'],
|
||||
[5 /* B */, 'B'],
|
||||
]
|
||||
const xrStandardLeftButtonsMap = [
|
||||
[0 /* trigger */, 'Left Trigger'],
|
||||
[1 /* squeeze */, 'Left Bumper'],
|
||||
// need to think of a way to support touchpad input
|
||||
[3 /* Thumbstick Press */, 'Left Stick'],
|
||||
[4 /* A */, 'X'],
|
||||
[5 /* B */, 'Y'],
|
||||
]
|
||||
const remapButtons = (rightButtons, leftButtons) => {
|
||||
// return remapped buttons
|
||||
const remapped = []
|
||||
const remapWithMap = (buttons, map) => {
|
||||
for (const [index, standardName] of map) {
|
||||
const standardMappingIndex = standardButtonsMap.findIndex((aliases) => aliases.find(alias => standardName === alias))
|
||||
remapped[standardMappingIndex] = buttons[index]
|
||||
}
|
||||
}
|
||||
remapWithMap(rightButtons, xrStandardRightButtonsMap)
|
||||
remapWithMap(leftButtons, xrStandardLeftButtonsMap)
|
||||
return remapped
|
||||
}
|
||||
const remapAxes = (axesRight, axesLeft) => {
|
||||
// 0, 1 are reserved for touch
|
||||
return [
|
||||
axesLeft[2],
|
||||
axesLeft[3],
|
||||
axesRight[2],
|
||||
axesRight[3]
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue