UNPKG

@pmndrs/xr

Version:
94 lines (93 loc) 4.89 kB
import { Euler, MathUtils, Quaternion, Vector3 } from 'three'; // useXRControllerLocomotion defaults and constants const defaultSpeed = 2; const defaultSmoothTurningSpeed = 2; const defaultSnapDegrees = 45; const defaultDeadZone = 0.5; const thumbstickPropName = 'xr-standard-thumbstick'; const vectorHelper = new Vector3(); const quaternionHelper = new Quaternion(); const eulerHelper = new Euler(); const positionHelper = new Vector3(); const scaleHelper = new Vector3(); /** * Function for handling controller based locomotion in VR * @param target Either an `Object`, or a callback function. Recieves translation and rotation input (required). * @param translationOptions Options that control the translation of the user. Set to `false` to disable. * @param translationOptions.speed The speed at which the user moves. * @param rotationOptions Options that control the rotation of the user. Set to `false` to disable. * @param rotationOptions.deadZone How far the joystick must be pushed to trigger a turn. * @param rotationOptions.type Controls how rotation using the controller functions. Can be either 'smooth' or 'snap'. * @param rotationOptions.degrees If `type` is 'snap', this specifies the number of degrees to snap the user's view by. * @param rotationOptions.speed If `type` is 'smooth', this specifies the speed at which the user's view rotates. * @param translationControllerHand Specifies which hand will control the translation. Can be either 'left' or 'right'. */ export function createXRControllerLocomotionUpdate() { let canRotate = true; return (target, store, camera, delta, translationOptions = {}, rotationOptions = {}, translationControllerHand = 'left', ...params) => { const { inputSourceStates } = store.getState(); const rotationControllerHand = translationControllerHand === 'left' ? 'right' : 'left'; const translationController = inputSourceStates.find((state) => isControllerWithHandedness(state, translationControllerHand)); const rotationController = inputSourceStates.find((state) => isControllerWithHandedness(state, rotationControllerHand)); if (translationController == null || rotationController == null) { return; } const translationThumbstickState = translationController.gamepad[thumbstickPropName]; const translationXAxis = translationThumbstickState?.xAxis ?? 0; const translationYAxis = translationThumbstickState?.yAxis ?? 0; const rotationXAxis = rotationController.gamepad[thumbstickPropName]?.xAxis ?? 0; //handle rotation let yRotationChange; if (rotationOptions !== false) { if (rotationOptions === true) { rotationOptions = {}; } if (rotationOptions.type === 'smooth') { if (Math.abs(rotationXAxis) > (rotationOptions.deadZone ?? defaultDeadZone)) { yRotationChange = (rotationXAxis < 0 ? -1 : 1) * delta * (rotationOptions.speed ?? defaultSmoothTurningSpeed); } } else { if (Math.abs(rotationXAxis) < (rotationOptions.deadZone ?? defaultDeadZone)) { canRotate = true; } else if (canRotate) { canRotate = false; yRotationChange = (rotationXAxis > 0 ? -1 : 1) * MathUtils.degToRad(rotationOptions.degrees ?? defaultSnapDegrees); } } } //handle translation const translationChanged = translationXAxis != 0 || translationYAxis != 0; if (translationOptions !== false && translationChanged) { if (translationOptions === true) { translationOptions = {}; } const { speed = defaultSpeed } = translationOptions; vectorHelper.set(translationXAxis * speed, 0, translationYAxis * speed); camera.matrixWorld.decompose(positionHelper, quaternionHelper, scaleHelper); vectorHelper.applyQuaternion(quaternionHelper); if (yRotationChange) { vectorHelper.applyEuler(eulerHelper.set(0, yRotationChange, 0, 'YXZ')); } } if (!translationChanged && yRotationChange == null) { return; } //apply translation and rotation: if (typeof target === 'function') { target(vectorHelper, yRotationChange ?? 0, ...params); return; } if (target == null) { return; } target.position.x += vectorHelper.x * delta; target.position.z += vectorHelper.z * delta; target.rotation.y += yRotationChange ?? 0; }; } function isControllerWithHandedness(state, handedness) { return state.type === 'controller' && state.inputSource.handedness === handedness; }