diff --git a/devtool/EmulatorDevtools.tsx b/devtool/EmulatorDevtools.tsx deleted file mode 100644 index b381c4c..0000000 --- a/devtool/EmulatorDevtools.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react' -import { useHookstate, useImmediateEffect, useMutableState } from '@ir-engine/hyperflux' -import Button from '@ir-engine/ui/src/primitives/tailwind/Button' -import { endXRSession, requestXRSession } from '@ir-engine/spatial/src/xr/XRSessionFunctions' - -import { EmulatorSettings, emulatorStates } from './js/emulatorStates.js' -import EmulatedDevice from './js/emulatedDevice.js' -import { syncDevicePose } from './js/messenger.js' -import Devtool from './jsx/app' -import devtoolCSS from './styles/index.css?inline' -import { overrideXR } from '../src/functions/xrBotHookFunctions.js' - -import 'bootstrap/dist/css/bootstrap.min.css' -import 'bootstrap' -import { XRState } from '@ir-engine/spatial/src/xr/XRState.js' - -const setup = async (mode: 'immersive-vr' | 'immersive-ar') => { - await overrideXR({ mode }) - await EmulatorSettings.instance.load() - const device = new EmulatedDevice() - device.on('pose', syncDevicePose) - ;(emulatorStates as any).emulatedDevice = device - - return device -} - -export const EmulatorDevtools = (props: { mode: 'immersive-vr' | 'immersive-ar' }) => { - const xrState = useMutableState(XRState) - const xrActive = xrState.sessionActive.value && !xrState.requestingSession.value - - const deviceState = useHookstate(null as null | EmulatedDevice) - useImmediateEffect(() => { - setup(props.mode).then((device) => { - deviceState.set(device) - }) - }, []) - - const toggleXR = async () => { - if (xrActive) { - endXRSession() - } else { - requestXRSession({ mode: props.mode }) - } - } - - const togglePlacement = () => { - if (xrState.scenePlacementMode.value !== 'placing') { - xrState.scenePlacementMode.set('placing') - xrState.sceneScaleAutoMode.set(false) - xrState.sceneScaleTarget.set(0.1) - } else { - xrState.scenePlacementMode.set('placed') - } - } - - return ( - <> - -
-
- - {props.mode === 'immersive-ar' && ( - - )} -
- {deviceState.value && } -
- - ) -} diff --git a/devtool/assets/headset.glb b/devtool/assets/headset.glb deleted file mode 100644 index 485e243..0000000 Binary files a/devtool/assets/headset.glb and /dev/null differ diff --git a/devtool/assets/images/auto-return.png b/devtool/assets/images/auto-return.png deleted file mode 100644 index ac294f8..0000000 Binary files a/devtool/assets/images/auto-return.png and /dev/null differ diff --git a/devtool/assets/images/button1-left.png b/devtool/assets/images/button1-left.png deleted file mode 100644 index ac16bca..0000000 Binary files a/devtool/assets/images/button1-left.png and /dev/null differ diff --git a/devtool/assets/images/button1-right.png b/devtool/assets/images/button1-right.png deleted file mode 100644 index 4343c70..0000000 Binary files a/devtool/assets/images/button1-right.png and /dev/null differ diff --git a/devtool/assets/images/button2-left.png b/devtool/assets/images/button2-left.png deleted file mode 100644 index c2222d7..0000000 Binary files a/devtool/assets/images/button2-left.png and /dev/null differ diff --git a/devtool/assets/images/button2-right.png b/devtool/assets/images/button2-right.png deleted file mode 100644 index 88d7136..0000000 Binary files a/devtool/assets/images/button2-right.png and /dev/null differ diff --git a/devtool/assets/images/delete.png b/devtool/assets/images/delete.png deleted file mode 100644 index efe5c73..0000000 Binary files a/devtool/assets/images/delete.png and /dev/null differ diff --git a/devtool/assets/images/exit.png b/devtool/assets/images/exit.png deleted file mode 100644 index f7a1a72..0000000 Binary files a/devtool/assets/images/exit.png and /dev/null differ diff --git a/devtool/assets/images/gamepad.png b/devtool/assets/images/gamepad.png deleted file mode 100644 index 97cb605..0000000 Binary files a/devtool/assets/images/gamepad.png and /dev/null differ diff --git a/devtool/assets/images/grip-left.png b/devtool/assets/images/grip-left.png deleted file mode 100644 index 926f292..0000000 Binary files a/devtool/assets/images/grip-left.png and /dev/null differ diff --git a/devtool/assets/images/grip-right.png b/devtool/assets/images/grip-right.png deleted file mode 100644 index 7f3ad8f..0000000 Binary files a/devtool/assets/images/grip-right.png and /dev/null differ diff --git a/devtool/assets/images/hand-pose.png b/devtool/assets/images/hand-pose.png deleted file mode 100644 index 28d7f3b..0000000 Binary files a/devtool/assets/images/hand-pose.png and /dev/null differ diff --git a/devtool/assets/images/hand-tracking.png b/devtool/assets/images/hand-tracking.png deleted file mode 100644 index 10fadea..0000000 Binary files a/devtool/assets/images/hand-tracking.png and /dev/null differ diff --git a/devtool/assets/images/headset-type.png b/devtool/assets/images/headset-type.png deleted file mode 100644 index 83358d2..0000000 Binary files a/devtool/assets/images/headset-type.png and /dev/null differ diff --git a/devtool/assets/images/headset.png b/devtool/assets/images/headset.png deleted file mode 100644 index cc47029..0000000 Binary files a/devtool/assets/images/headset.png and /dev/null differ diff --git a/devtool/assets/images/horizontal.png b/devtool/assets/images/horizontal.png deleted file mode 100644 index 07e1db7..0000000 Binary files a/devtool/assets/images/horizontal.png and /dev/null differ diff --git a/devtool/assets/images/info.png b/devtool/assets/images/info.png deleted file mode 100644 index bf27215..0000000 Binary files a/devtool/assets/images/info.png and /dev/null differ diff --git a/devtool/assets/images/joystick.png b/devtool/assets/images/joystick.png deleted file mode 100644 index b248180..0000000 Binary files a/devtool/assets/images/joystick.png and /dev/null differ diff --git a/devtool/assets/images/keyboard.png b/devtool/assets/images/keyboard.png deleted file mode 100644 index ac58759..0000000 Binary files a/devtool/assets/images/keyboard.png and /dev/null differ diff --git a/devtool/assets/images/left-controller.png b/devtool/assets/images/left-controller.png deleted file mode 100644 index eac798c..0000000 Binary files a/devtool/assets/images/left-controller.png and /dev/null differ diff --git a/devtool/assets/images/left-hand.png b/devtool/assets/images/left-hand.png deleted file mode 100644 index cee6edd..0000000 Binary files a/devtool/assets/images/left-hand.png and /dev/null differ diff --git a/devtool/assets/images/lock.png b/devtool/assets/images/lock.png deleted file mode 100644 index 5f79253..0000000 Binary files a/devtool/assets/images/lock.png and /dev/null differ diff --git a/devtool/assets/images/move.png b/devtool/assets/images/move.png deleted file mode 100644 index d12b091..0000000 Binary files a/devtool/assets/images/move.png and /dev/null differ diff --git a/devtool/assets/images/play.png b/devtool/assets/images/play.png deleted file mode 100644 index e512702..0000000 Binary files a/devtool/assets/images/play.png and /dev/null differ diff --git a/devtool/assets/images/polyfill-on.png b/devtool/assets/images/polyfill-on.png deleted file mode 100644 index c4fe0b0..0000000 Binary files a/devtool/assets/images/polyfill-on.png and /dev/null differ diff --git a/devtool/assets/images/pose.png b/devtool/assets/images/pose.png deleted file mode 100644 index 6551a82..0000000 Binary files a/devtool/assets/images/pose.png and /dev/null differ diff --git a/devtool/assets/images/press.png b/devtool/assets/images/press.png deleted file mode 100644 index 443404b..0000000 Binary files a/devtool/assets/images/press.png and /dev/null differ diff --git a/devtool/assets/images/reset.png b/devtool/assets/images/reset.png deleted file mode 100644 index 476b652..0000000 Binary files a/devtool/assets/images/reset.png and /dev/null differ diff --git a/devtool/assets/images/revert.png b/devtool/assets/images/revert.png deleted file mode 100644 index 0974e7d..0000000 Binary files a/devtool/assets/images/revert.png and /dev/null differ diff --git a/devtool/assets/images/right-controller.png b/devtool/assets/images/right-controller.png deleted file mode 100644 index 27888ab..0000000 Binary files a/devtool/assets/images/right-controller.png and /dev/null differ diff --git a/devtool/assets/images/right-hand.png b/devtool/assets/images/right-hand.png deleted file mode 100644 index 0047730..0000000 Binary files a/devtool/assets/images/right-hand.png and /dev/null differ diff --git a/devtool/assets/images/roomscale.png b/devtool/assets/images/roomscale.png deleted file mode 100644 index 24fbf51..0000000 Binary files a/devtool/assets/images/roomscale.png and /dev/null differ diff --git a/devtool/assets/images/rotation.png b/devtool/assets/images/rotation.png deleted file mode 100644 index 4aea38e..0000000 Binary files a/devtool/assets/images/rotation.png and /dev/null differ diff --git a/devtool/assets/images/save-copy.png b/devtool/assets/images/save-copy.png deleted file mode 100644 index 5509eab..0000000 Binary files a/devtool/assets/images/save-copy.png and /dev/null differ diff --git a/devtool/assets/images/save.png b/devtool/assets/images/save.png deleted file mode 100644 index 3629043..0000000 Binary files a/devtool/assets/images/save.png and /dev/null differ diff --git a/devtool/assets/images/send.png b/devtool/assets/images/send.png deleted file mode 100644 index 7908702..0000000 Binary files a/devtool/assets/images/send.png and /dev/null differ diff --git a/devtool/assets/images/settings.png b/devtool/assets/images/settings.png deleted file mode 100644 index 1e83b62..0000000 Binary files a/devtool/assets/images/settings.png and /dev/null differ diff --git a/devtool/assets/images/stereo.png b/devtool/assets/images/stereo.png deleted file mode 100644 index 0bd7d89..0000000 Binary files a/devtool/assets/images/stereo.png and /dev/null differ diff --git a/devtool/assets/images/sticky.png b/devtool/assets/images/sticky.png deleted file mode 100644 index ae1a79c..0000000 Binary files a/devtool/assets/images/sticky.png and /dev/null differ diff --git a/devtool/assets/images/trash.png b/devtool/assets/images/trash.png deleted file mode 100644 index 5ce87e0..0000000 Binary files a/devtool/assets/images/trash.png and /dev/null differ diff --git a/devtool/assets/images/trigger-left.png b/devtool/assets/images/trigger-left.png deleted file mode 100644 index 02929f9..0000000 Binary files a/devtool/assets/images/trigger-left.png and /dev/null differ diff --git a/devtool/assets/images/trigger-right.png b/devtool/assets/images/trigger-right.png deleted file mode 100644 index 52b9113..0000000 Binary files a/devtool/assets/images/trigger-right.png and /dev/null differ diff --git a/devtool/assets/images/undo.png b/devtool/assets/images/undo.png deleted file mode 100644 index ce84255..0000000 Binary files a/devtool/assets/images/undo.png and /dev/null differ diff --git a/devtool/assets/images/vertical.png b/devtool/assets/images/vertical.png deleted file mode 100644 index d6ef3fb..0000000 Binary files a/devtool/assets/images/vertical.png and /dev/null differ diff --git a/devtool/assets/left-controller.glb b/devtool/assets/left-controller.glb deleted file mode 100644 index 56d914d..0000000 Binary files a/devtool/assets/left-controller.glb and /dev/null differ diff --git a/devtool/assets/left-hand.glb b/devtool/assets/left-hand.glb deleted file mode 100644 index 9feae89..0000000 Binary files a/devtool/assets/left-hand.glb and /dev/null differ diff --git a/devtool/assets/right-controller.glb b/devtool/assets/right-controller.glb deleted file mode 100644 index bb3d9df..0000000 Binary files a/devtool/assets/right-controller.glb and /dev/null differ diff --git a/devtool/assets/right-hand.glb b/devtool/assets/right-hand.glb deleted file mode 100644 index 2c29a7d..0000000 Binary files a/devtool/assets/right-hand.glb and /dev/null differ diff --git a/devtool/js/actions.js b/devtool/js/actions.js deleted file mode 100644 index b7a9911..0000000 --- a/devtool/js/actions.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/** - * Events triggered by the emulator UI and sent to the content script - */ -export const EMULATOR_ACTIONS = { - HEADSET_POSE_CHANGE: 'ea-headset-pose-change', - CONTROLLER_POSE_CHANGE: 'ea-controller-pose-change', - CONTROLLER_VISIBILITY_CHANGE: 'ea-controller-visibility-change', - BUTTON_STATE_CHANGE: 'ea-button-state-change', - ANALOG_VALUE_CHANGE: 'ea-analog-value-change', - DEVICE_TYPE_CHANGE: 'ea-device-type-change', - STEREO_TOGGLE: 'ea-stereo-toggle', - KEYBOARD_EVENT: 'ea-keyboard-event', - EXIT_IMMERSIVE: 'ea-exit-immersive', - ROOM_DIMENSION_CHANGE: 'ea-room-dimension-change', - EXCLUDE_POLYFILL: 'ea-exclude-polyfill', - INPUT_MODE_CHANGE: 'ea-input-mode-change', - HAND_POSE_CHANGE: 'ea-hand-pose-change', - HAND_VISIBILITY_CHANGE: 'ea-hand-visibility-change', - PINCH_VALUE_CHANGE: 'ea-pinch-value-change', - USER_OBJECTS_CHANGE: 'ea-user-objects-change', -}; - -/** - * Events triggered by the content script and caught and processed by the custom WebXR Polyfill - */ -export const POLYFILL_ACTIONS = EMULATOR_ACTIONS -// { -// HEADSET_POSE_CHANGE: 'pa-headset-pose-change', -// CONTROLLER_POSE_CHANGE: 'pa-controller-pose-change', -// CONTROLLER_VISIBILITY_CHANGE: 'pa-controller-visibility-change', -// BUTTON_STATE_CHANGE: 'pa-button-state-change', -// ANALOG_VALUE_CHANGE: 'pa-analog-value-change', -// DEVICE_TYPE_CHANGE: 'pa-device-type-change', -// STEREO_TOGGLE: 'pa-stereo-toggle', -// KEYBOARD_EVENT: 'pa-keyboard-event', -// EXIT_IMMERSIVE: 'pa-exit-immersive', -// DEVICE_INIT: 'pa-device-init', -// ROOM_DIMENSION_CHANGE: 'pa-room-dimension-change', -// INPUT_MODE_CHANGE: 'pa-input-mode-change', -// HAND_POSE_CHANGE: 'pa-hand-pose-change', -// HAND_VISIBILITY_CHANGE: 'pa-hand-visibility-change', -// PINCH_VALUE_CHANGE: 'pa-pinch-value-change', -// USER_OBJECTS_CHANGE: 'pa-user-objects-change', -// }; - -/** - * Events triggered from the client side that are caught by the content script and then relayed back to the emulator side - */ -export const CLIENT_ACTIONS = { - ENTER_IMMERSIVE: 'ca-enter-immersive', - EXIT_IMMERSIVE: 'ca-exit-immersive', - PING: 'ca-ping', -}; diff --git a/devtool/js/constants.js b/devtool/js/constants.js deleted file mode 100644 index 3eeee5c..0000000 --- a/devtool/js/constants.js +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -export const PRESS_AND_RELEASE_DURATION = 250; - -export const BUTTON_POLYFILL_INDEX_MAPPING = { - joystick: 0, - trigger: 1, - grip: 2, - button1: 3, - button2: 4, -}; - -export const DEVICE = { - HEADSET: '0', - INPUT_RIGHT: '2', - INPUT_LEFT: '3', -}; - -export const OBJECT_NAME = {}; -OBJECT_NAME[DEVICE.HEADSET] = 'headset'; -OBJECT_NAME[DEVICE.INPUT_LEFT] = 'left-controller'; -OBJECT_NAME[DEVICE.INPUT_RIGHT] = 'right-controller'; - -export const DEFAULT_TRANSFORMS = {}; -DEFAULT_TRANSFORMS[DEVICE.HEADSET] = { - position: [0, 1.7, 0], - rotation: [0, 0, 0, 'XYZ'], -}; -DEFAULT_TRANSFORMS[DEVICE.INPUT_RIGHT] = { - position: [0.25, 1.5, -0.4], - rotation: [0, 0, 0, 'XYZ'], -}; -DEFAULT_TRANSFORMS[DEVICE.INPUT_LEFT] = { - position: [-0.25, 1.5, -0.4], - rotation: [0, 0, 0, 'XYZ'], -}; - -export const CONTROLLER_STRINGS = {}; -CONTROLLER_STRINGS[DEVICE.INPUT_LEFT] = { - name: 'left-controller', - displayName: 'Left Controller', - handedness: 'left', - button1: 'ButtonX', - button2: 'ButtonY', -}; -CONTROLLER_STRINGS[DEVICE.INPUT_RIGHT] = { - name: 'right-controller', - displayName: 'Right Controller', - handedness: 'right', - button1: 'ButtonA', - button2: 'ButtonB', -}; - -export const HAND_STRINGS = {}; -HAND_STRINGS[DEVICE.INPUT_LEFT] = { - name: 'left-hand', - displayName: 'Left Hand', - handedness: 'left', -}; -HAND_STRINGS[DEVICE.INPUT_RIGHT] = { - name: 'right-hand', - displayName: 'Right Hand', - handedness: 'right', -}; - -export const KEYBOARD_CONTROL_MAPPING = { - left: { - joystickLeft: 'a', - joystickRight: 'd', - joystickForward: 'w', - joystickBackward: 's', - trigger: 'e', - grip: 'q', - button1: 'x', - button2: 'z', - joystick: 'c', - }, - right: { - joystickLeft: 'ArrowLeft', - joystickRight: 'ArrowRight', - joystickForward: 'ArrowUp', - joystickBackward: 'ArrowDown', - trigger: 'Enter', - grip: 'Shift', - button1: "'", - button2: '/', - joystick: '.', - }, -}; - -export const GAMEPAD_ID_TO_INPUT_ID_MAPPING = { - 3: 'joystick', - 5: 'button2', - 4: 'button1', - 0: 'trigger', - 1: 'grip', -}; - -export const SEMANTIC_LABELS = { - Desk: 'desk', - Couch: 'couch', - Floor: 'floor', - Ceiling: 'ceiling', - Wall: 'wall', - Door: 'door', - Window: 'window', - Table: 'table', - Shelf: 'shelf', - Bed: 'bed', - Screen: 'screen', - Lamp: 'lamp', - Plant: 'plant', - WallArt: 'wall art', - Other: 'other', -}; - -export const TRIGGER_MODES = ['slow', 'normal', 'fast', 'turbo']; - -export const TRIGGER_CONFIG = { - slow: { - interval: 20, - holdTime: 100, - }, - normal: { - interval: 10, - holdTime: 50, - }, - fast: { - interval: 5, - holdTime: 10, - }, - turbo: { - interval: 1, - holdTime: 1, - }, -}; diff --git a/devtool/js/devices.js b/devtool/js/devices.js deleted file mode 100644 index 468698a..0000000 --- a/devtool/js/devices.js +++ /dev/null @@ -1,235 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -export const DEVICE_DEFINITIONS = { - 'Oculus Rift CV1': { - id: 'Oculus Rift CV1', - name: 'Oculus Rift CV1', - shortName: 'Rift CV1', - profile: 'oculus-touch', - modes: ['inline', 'immersive-vr'], - headset: { - hasPosition: true, - hasRotation: true, - }, - controllers: [ - { - id: 'Oculus Touch (Left)', - buttonNum: 7, - /** - * this is not the index in gamepad.buttons, but the index used for input remapping in WebXR Polyfill - * @see https://github.com/immersive-web/webxr-polyfill/blob/main/src/devices/GamepadMappings.js - */ - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'left', - }, - { - id: 'Oculus Touch (Right)', - buttonNum: 7, - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'right', - }, - ], - polyfillInputMapping: { - axes: [2, 3, 0, 1], - buttons: [1, 2, null, 0, 3, 4, null], - }, - }, - 'Oculus Rift S': { - id: 'Oculus Rift S', - name: 'Oculus Rift S', - shortName: 'Rift S', - profile: 'oculus-touch-v2', - modes: ['inline', 'immersive-vr'], - headset: { - hasPosition: true, - hasRotation: true, - }, - controllers: [ - { - id: 'Oculus Touch V2 (Left)', - buttonNum: 7, - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'left', - }, - { - id: 'Oculus Touch V2 (Right)', - buttonNum: 7, - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'right', - }, - ], - polyfillInputMapping: { - axes: [2, 3, 0, 1], - buttons: [1, 2, null, 0, 3, 4, null], - }, - }, - 'Oculus Quest': { - id: 'Oculus Quest', - name: 'Oculus Quest', - shortName: 'Quest 1', - profile: 'oculus-touch-v2', - modes: ['inline', 'immersive-vr'], - headset: { - hasPosition: true, - hasRotation: true, - }, - controllers: [ - { - id: 'Oculus Touch V2 (Left)', - buttonNum: 7, - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'left', - }, - { - id: 'Oculus Touch V2 (Right)', - buttonNum: 7, - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'right', - }, - ], - polyfillInputMapping: { - axes: [2, 3, 0, 1], - buttons: [1, 2, null, 0, 3, 4, null], - }, - }, - 'Oculus Quest 2': { - id: 'Oculus Quest 2', - name: 'Oculus Quest 2', - shortName: 'Quest 2', - profile: 'oculus-touch-v3', - modes: ['inline', 'immersive-vr', 'immersive-ar'], - headset: { - hasPosition: true, - hasRotation: true, - }, - controllers: [ - { - id: 'Oculus Touch V3 (Left)', - buttonNum: 7, - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'left', - }, - { - id: 'Oculus Touch V3 (Right)', - buttonNum: 7, - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'right', - }, - ], - polyfillInputMapping: { - axes: [2, 3, 0, 1], - buttons: [1, 2, null, 0, 3, 4, null], - }, - }, - 'Meta Quest Pro': { - id: 'Meta Quest Pro', - name: 'Meta Quest Pro', - shortName: 'Quest Pro', - profile: 'meta-quest-touch-pro', - modes: ['inline', 'immersive-vr', 'immersive-ar'], - headset: { - hasPosition: true, - hasRotation: true, - }, - controllers: [ - { - id: 'Meta Quest Touch Pro (Left)', - buttonNum: 7, - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'left', - }, - { - id: 'Meta Quest Touch Pro (Right)', - buttonNum: 7, - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'right', - }, - ], - polyfillInputMapping: { - axes: [2, 3, 0, 1], - buttons: [1, 2, null, 0, 3, 4, null], - }, - }, - 'Meta Quest 3': { - id: 'Meta Quest 3', - name: 'Meta Quest 3', - shortName: 'Quest 3', - profile: 'meta-quest-touch-plus', - modes: ['inline', 'immersive-vr', 'immersive-ar'], - headset: { - hasPosition: true, - hasRotation: true, - }, - controllers: [ - { - id: 'Meta Quest Touch Plus (Left)', - buttonNum: 7, - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'left', - }, - { - id: 'Meta Quest Touch Plus (Right)', - buttonNum: 7, - primaryButtonIndex: 1, - primarySqueezeButtonIndex: 2, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - handedness: 'right', - }, - ], - polyfillInputMapping: { - axes: [2, 3, 0, 1], - buttons: [1, 2, null, 0, 3, 4, null], - }, - }, -}; diff --git a/devtool/js/emulatedDevice.js b/devtool/js/emulatedDevice.js deleted file mode 100644 index 8f7cde2..0000000 --- a/devtool/js/emulatedDevice.js +++ /dev/null @@ -1,537 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import * as THREE from 'three'; - -import { - CONTROLLER_STRINGS, - DEVICE, - HAND_STRINGS, - OBJECT_NAME, -} from './constants'; -import { EmulatorSettings, emulatorStates } from './emulatorStates'; - -import { BoxLineGeometry } from 'three/examples/jsm/geometries/BoxLineGeometry.js'; -import { EventEmitter } from 'events'; -import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; -import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'; -import { TransformControls } from 'three/examples/jsm/controls/TransformControls.js'; -import { generateUUID } from 'three/src/math/MathUtils.js'; -import { updateUserObjects } from './messenger'; - -import config from '@ir-engine/common/src/config'; -const assetURL = config.client.fileServer + '/projects/ir-engine/ir-bot/devtool' - -const SELECTION_MOUSE_DOWN_THRESHOLD = 300; - -const isNumber = function isNumber(value) { - return typeof value === 'number' && isFinite(value); -}; - -export default class EmulatedDevice extends EventEmitter { - constructor() { - super(); - this._renderer = new THREE.WebGLRenderer({ antialias: true }); - this._renderer.setPixelRatio(window.devicePixelRatio); - this._renderer.setSize(1, 1); - this._renderer.domElement.style.position = 'absolute'; - - this._scene = new THREE.Scene(); - this._scene.background = new THREE.Color(0x505050); - this._scene.add(new THREE.DirectionalLight(0xffffff, 1)); - this._scene.add(new THREE.AmbientLight(0x404040, 2)); - - this._camera = new THREE.PerspectiveCamera(45, 1 / 1, 0.1, 100); - this._camera.position.set(-1.5, 1.7, 2); - this._camera.lookAt(new THREE.Vector3(0, 1.6, 0)); - - this._controllerMeshes = {}; - this._controllerMeshesHidden = {}; - this._handMeshes = {}; - this._handMeshesHidden = {}; - - this._labelContainer = document.createElement('div'); - - const oc = new OrbitControls(this._camera, this.canvas); - oc.addEventListener('change', this.render.bind(this)); - oc.target.set(0, 1.6, 0); - oc.update(); - this._orbitControls = oc; - - const loader = new GLTFLoader(); - this._transformControls = {}; - Object.values(DEVICE).forEach((deviceKey) => { - const node = new THREE.Group(); - node.position.fromArray( - EmulatorSettings.instance.defaultPose[deviceKey].position, - ); - node.rotation.fromArray( - EmulatorSettings.instance.defaultPose[deviceKey].rotation, - ); - emulatorStates.assetNodes[deviceKey] = node; - this._scene.add(node); - - // add device node mesh to parent - loader.load(`${assetURL}/assets/${OBJECT_NAME[deviceKey]}.glb`, (gltf) => { - const mesh = gltf.scene; - mesh.scale.setScalar(2); - mesh.rotateY(Math.PI); - mesh.traverse((child) => { - child.userData['deviceKey'] = deviceKey; - }); - node.add(mesh); - if (CONTROLLER_STRINGS[deviceKey]) { - this._controllerMeshes[deviceKey] = mesh; - mesh.visible = EmulatorSettings.instance.inputMode === 'controllers'; - this._controllerMeshesHidden[deviceKey] = false; - } - this.render(); - }); - - if (HAND_STRINGS[deviceKey]) { - loader.load(`${assetURL}/assets/${HAND_STRINGS[deviceKey].name}.glb`, (gltf) => { - const mesh = gltf.scene; - mesh.scale.setScalar(2); - mesh.rotateY(Math.PI); - mesh.traverse((child) => { - child.userData['deviceKey'] = deviceKey; - }); - node.add(mesh); - this._handMeshes[deviceKey] = mesh; - mesh.visible = EmulatorSettings.instance.inputMode === 'hands'; - this._handMeshesHidden[deviceKey] = false; - this.render(); - }); - } - - // setup transform control - const controls = new TransformControls(this._camera, this.canvas); - controls.attach(node); - controls.enabled = false; - controls.visible = false; - controls.addEventListener('mouseDown', () => (oc.enabled = false)); - controls.addEventListener('mouseUp', () => (oc.enabled = true)); - controls.addEventListener('change', () => { - this._emitPoseEvent(deviceKey); - this.render(); - }); - this._transformControls[deviceKey] = controls; - this._scene.add(controls); - }); - - this._userObjects = {}; - this._recoverObjects(); - - // check device node selection by raycast - this._raycaster = new THREE.Raycaster(); - this._mouseVec2 = new THREE.Vector2(); - this._mouseDownTime = null; - this._selectedDeviceKey = null; - this.canvas.addEventListener('mousedown', (event) => { - this._selectedDeviceKey = this._findSelectedDeviceNode(event); - this._mouseDownTime = performance.now(); - }); - this.canvas.addEventListener('mouseup', () => { - if (this._selectedDeviceKey != null) { - const currentTime = performance.now(); - if ( - currentTime - this._mouseDownTime < - SELECTION_MOUSE_DOWN_THRESHOLD - ) { - this.toggleControlMode(this._selectedDeviceKey); - oc.enabled = true; - } - } - }); - - this.updateRoom(); - } - - _emitPoseEvent(deviceKey) { - const node = this.getDeviceNode(deviceKey); - this.emit('pose', { - deviceKey, - position: node.position.toArray(), - rotation: node.rotation.toArray(), - quaternion: node.quaternion.toArray(), - }); - } - - _findSelectedDeviceNode(mouseEvent) { - const rect = this.canvas.getBoundingClientRect(); - const point = { - x: (mouseEvent.clientX - rect.left) / rect.width, - y: (mouseEvent.clientY - rect.top) / rect.height, - }; - this._mouseVec2.set(point.x * 2 - 1, -(point.y * 2) + 1); - this._raycaster.setFromCamera(this._mouseVec2, this._camera); - const intersect = this._raycaster.intersectObjects( - [ - ...Object.values(emulatorStates.assetNodes), - ...Object.values(this._userObjects), - ], - true, - )[0]; - - return ( - intersect?.object.userData['deviceKey'] ?? - intersect?.object.userData['userObjectId'] - ); - } - - updateRoom() { - const dimension = EmulatorSettings.instance.roomDimension; - if (this._roomObject) { - this._scene.remove(this._roomObject); - } - this._roomObject = new THREE.LineSegments( - new BoxLineGeometry( - dimension.x, - dimension.y, - dimension.z, - Math.ceil(dimension.x * 2), - Math.ceil(dimension.y * 2), - Math.ceil(dimension.z * 2), - ), - new THREE.LineBasicMaterial({ color: 0x808080 }), - ); - this._roomObject.geometry.translate(0, dimension.y / 2, 0); - this._scene.add(this._roomObject); - this.render(); - } - - addObject(object, semanticLabel, idOverride = null) { - this._scene.add(object); - const controls = new TransformControls(this._camera, this.canvas); - controls.attach(object); - controls.enabled = false; - controls.visible = false; - controls.addEventListener( - 'mouseDown', - () => (this._orbitControls.enabled = false), - ); - controls.addEventListener('mouseUp', () => { - this._orbitControls.enabled = true; - this._updateObjects(); - }); - controls.addEventListener('change', () => { - this.render(); - }); - this._scene.add(controls); - const userObjectId = idOverride ?? generateUUID(); - this._transformControls[userObjectId] = controls; - this._userObjects[userObjectId] = object; - const label = document.createElement('div'); - label.classList.add('semantic-label'); - label.innerHTML = semanticLabel; - this._labelContainer.appendChild(label); - object.userData = { userObjectId, controls, semanticLabel, label }; - if (idOverride == null) { - this.render(); - } - return userObjectId; - } - - addMesh( - width, - height, - depth, - semanticLabel, - idOverride = null, - active = true, - ) { - if ( - !isNumber(width) || - !isNumber(height) || - !isNumber(depth) || - width * height * depth == 0 - ) { - return; - } - const object = new THREE.Mesh( - new THREE.BoxGeometry(width, height, depth), - new THREE.MeshPhongMaterial({ - color: 0xffffff * Math.random(), - transparent: true, - }), - ); - const userObjectId = this.addObject(object, semanticLabel, idOverride); - EmulatorSettings.instance.userObjects[userObjectId] = { - type: 'mesh', - active: true, - width, - height, - depth, - semanticLabel, - position: object.position.toArray(), - quaternion: object.quaternion.toArray(), - }; - this._toggleObjectVisibility(userObjectId, active); - EmulatorSettings.instance.write().then(updateUserObjects); - return object; - } - - addPlane( - width, - height, - isVertical, - semanticLabel, - idOverride = null, - active = true, - ) { - if (!isNumber(width) || !isNumber(height) || width * height == 0) { - return; - } - const planeGeometry = new THREE.PlaneGeometry(width, height); - planeGeometry.rotateX(Math.PI / 2); - const object = new THREE.Mesh( - planeGeometry, - new THREE.MeshPhongMaterial({ - color: 0xffffff * Math.random(), - side: THREE.DoubleSide, - transparent: true, - }), - ); - if (isVertical) { - object.rotateX(Math.PI / 2); - } - const userObjectId = this.addObject(object, semanticLabel, idOverride); - EmulatorSettings.instance.userObjects[userObjectId] = { - type: 'plane', - active: true, - width, - height, - isVertical, - semanticLabel, - position: object.position.toArray(), - quaternion: object.quaternion.toArray(), - }; - this._toggleObjectVisibility(userObjectId, active); - EmulatorSettings.instance.write().then(updateUserObjects); - return object; - } - - deleteSelectedObject() { - Object.entries(this._transformControls).forEach(([key, controls]) => { - if (controls.enabled) { - const object = this._userObjects[key]; - if (object) { - const { label } = object.userData; - this._labelContainer.removeChild(label); - controls.detach(); - this._scene.remove(object); - delete this._userObjects[key]; - controls.dispose(); - delete this._transformControls[key]; - this.render(); - delete EmulatorSettings.instance.userObjects[key]; - EmulatorSettings.instance.write().then(updateUserObjects); - } - } - }); - } - - _toggleObjectVisibility(objectId, active = undefined) { - const object = this._userObjects[objectId]; - if (object) { - const isActive = - active ?? !EmulatorSettings.instance.userObjects[objectId].active; - EmulatorSettings.instance.userObjects[objectId].active = isActive; - const { label, semanticLabel } = object.userData; - if (isActive) { - object.material.opacity = 1; - label.innerHTML = semanticLabel; - } else { - object.material.opacity = 0.4; - label.innerHTML = '[hidden] ' + semanticLabel; - } - } - } - - toggleSelectedObjectVisibility() { - Object.entries(this._transformControls).forEach(([objectId, controls]) => { - if (controls.enabled) { - this._toggleObjectVisibility(objectId); - this.render(); - EmulatorSettings.instance.write().then(updateUserObjects); - } - }); - } - - _updateObjects() { - Object.entries(this._transformControls).forEach( - ([userObjectId, controls]) => { - if (controls.enabled) { - const object = this._userObjects[userObjectId]; - if (object) { - EmulatorSettings.instance.userObjects[userObjectId].position = - object.position.toArray(); - EmulatorSettings.instance.userObjects[userObjectId].quaternion = - object.quaternion.toArray(); - } - } - }, - ); - EmulatorSettings.instance.write().then(updateUserObjects); - } - - _recoverObjects() { - Object.entries(EmulatorSettings.instance.userObjects).forEach( - ([userObjectId, objectData]) => { - const { - type, - active, - width, - height, - depth, - isVertical, - semanticLabel, - position, - quaternion, - } = objectData; - let object; - if (type === 'mesh') { - object = this.addMesh( - width, - height, - depth, - semanticLabel, - userObjectId, - active, - ); - } else if (type === 'plane') { - object = this.addPlane( - width, - height, - isVertical, - semanticLabel, - userObjectId, - active, - ); - } - if (object) { - object.position.fromArray(position); - object.quaternion.fromArray(quaternion); - } - }, - ); - } - - get canvas() { - return this._renderer.domElement; - } - - get labels() { - return this._labelContainer; - } - - getDeviceNode(deviceKey) { - return emulatorStates.assetNodes[deviceKey]; - } - - forceEmitPose() { - Object.values(DEVICE).forEach((deviceKey) => { - this._emitPoseEvent(deviceKey); - }); - } - - toggleControlMode(deviceKey, clearOthers = true) { - if (clearOthers) { - Object.entries(this._transformControls).forEach(([key, controls]) => { - if (key != deviceKey) { - controls.enabled = false; - controls.visible = false; - } - }); - } - const controls = this._transformControls[deviceKey]; - if (!controls.enabled) { - controls.enabled = true; - controls.visible = true; - controls.setMode('translate'); - } else if (controls.getMode() === 'translate') { - controls.setMode('rotate'); - } else { - controls.enabled = false; - controls.visible = false; - } - this.render(); - } - - toggleControllerVisibility(deviceKey, visible) { - this._controllerMeshesHidden[deviceKey] = !visible; - this.render(); - } - - toggleHandVisibility(deviceKey, visible) { - this._handMeshesHidden[deviceKey] = !visible; - this.render(); - } - - setDeviceTransform(deviceKey, position, rotation) { - const deviceNode = this.getDeviceNode(deviceKey); - if (deviceNode) { - deviceNode.position.fromArray(position); - deviceNode.rotation.fromArray(rotation); - this._emitPoseEvent(deviceKey); - this.render(); - } - } - - resetPose() { - Object.values(DEVICE).forEach((deviceKey) => { - const deviceNode = this.getDeviceNode(deviceKey); - deviceNode.position.fromArray( - EmulatorSettings.instance.defaultPose[deviceKey].position, - ); - deviceNode.rotation.fromArray( - EmulatorSettings.instance.defaultPose[deviceKey].rotation, - ); - this._emitPoseEvent(deviceKey); - }); - this.render(); - } - - render() { - Object.entries(this._handMeshes).forEach(([deviceKey, mesh]) => { - mesh.visible = - EmulatorSettings.instance.inputMode === 'hands' && - !this._handMeshesHidden[deviceKey]; - }); - Object.entries(this._controllerMeshes).forEach(([deviceKey, mesh]) => { - mesh.visible = - EmulatorSettings.instance.inputMode === 'controllers' && - !this._controllerMeshesHidden[deviceKey]; - }); - const parent = this.canvas.parentElement; - if (!parent) return; - const width = parent.offsetWidth; - const height = parent.offsetHeight; - if (width != this._lastWidth || height != this._lastHeight) { - this._camera.aspect = width / height; - this._camera.updateProjectionMatrix(); - this._renderer.setSize(width, height); - this._lastWidth = width; - this._lastHeight = height; - } - this._renderer.render(this._scene, this._camera); - - const sceneContainer = this._renderer.domElement.parentElement; - if (!sceneContainer) return; - - Object.values(this._userObjects).forEach((object) => { - const { label } = object.userData; - if (label) { - const screenVec = object.position.clone().project(this._camera); - screenVec.x = ((screenVec.x + 1) * sceneContainer.offsetWidth) / 2; - screenVec.y = (-(screenVec.y - 1) * sceneContainer.offsetHeight) / 2; - label.style.top = `${screenVec.y}px`; - label.style.left = `${screenVec.x}px`; - } - }); - } -} diff --git a/devtool/js/emulatorStates.js b/devtool/js/emulatorStates.js deleted file mode 100644 index 837e81c..0000000 --- a/devtool/js/emulatorStates.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { DEFAULT_TRANSFORMS } from './constants'; - -// eslint-disable-next-line no-undef -// const localStorage = chrome.storage.local; -const originalLocalStorage = window.localStorage; -const localStorage = { - get: (key, callback) => { - callback(originalLocalStorage.getItem(key)) - }, - set: (data, callback) => { - Object.entries(data).forEach(([key, value]) => { - originalLocalStorage.setItem(key, value) - }) - callback() - } -} - - -const STORAGE_KEY = 'immersive-web-emulator-settings'; -localStorage.set({ [STORAGE_KEY]: null }, () => {}); - -export const emulatorStates = { - inImmersive: false, - actionMappingOn: true, - assetNodes: {}, - controllers: { - 'left-controller': { - joystick: { - touched: false, - pressed: false, - valueX: 0, - valueY: 0, - }, - trigger: { - touched: false, - value: 0, - }, - grip: { - touched: false, - value: 0, - }, - button1: { - touched: false, - pressed: false, - }, - button2: { - touched: false, - pressed: false, - }, - }, - 'right-controller': { - joystick: { - touched: false, - pressed: false, - valueX: 0, - valueY: 0, - }, - trigger: { - touched: false, - value: 0, - }, - grip: { - touched: false, - value: 0, - }, - button1: { - touched: false, - pressed: false, - }, - button2: { - touched: false, - pressed: false, - }, - }, - }, - playbackInProgress: false, - pinchValues: { - 'left-hand': 0, - 'right-hand': 0, - }, - joysticks: {}, - buttons: {}, - sliders: {}, - emulatedDevice: null, -}; - -export class EmulatorSettings { - static get instance() { - if (!EmulatorSettings._instance) { - EmulatorSettings._instance = new EmulatorSettings(); - } - return EmulatorSettings._instance; - } - - constructor() { - this.stereoOn = false; - this.actionMappingOn = true; - this.defaultPose = DEFAULT_TRANSFORMS; - this.deviceKey = 'Meta Quest 3'; - this.keyboardMappingOn = true; - this.roomDimension = { x: 6, y: 3, z: 6 }; - this.polyfillExcludes = new Set(); - this.inputMode = 'controllers'; - this.handPoses = { - 'left-hand': 'relaxed', - 'right-hand': 'relaxed', - }; - this.userObjects = {}; - this.triggerMode = 'normal'; - } - - load() { - return new Promise((resolve) => { - localStorage.get(STORAGE_KEY, (result) => { - const settings = result[STORAGE_KEY] - ? JSON.parse(result[STORAGE_KEY]) - : null; - this.stereoOn = settings?.stereoOn ?? false; - this.actionMappingOn = settings?.actionMappingOn ?? true; - this.defaultPose = settings?.defaultPose ?? DEFAULT_TRANSFORMS; - this.deviceKey = settings?.deviceKey ?? 'Meta Quest 3'; - this.keyboardMappingOn = settings?.keyboardMappingOn ?? true; - this.roomDimension = settings?.roomDimension ?? { x: 6, y: 3, z: 6 }; - this.polyfillExcludes = new Set(settings?.polyfillExcludes ?? []); - this.inputMode = settings?.inputMode ?? 'controllers'; - this.handPoses = settings?.handPoses ?? this.handPoses; - this.userObjects = settings?.userObjects ?? {}; - this.triggerMode = settings?.triggerMode ?? 'normal'; - resolve(result); - }); - }); - } - - write() { - const settings = {}; - settings[STORAGE_KEY] = JSON.stringify({ - stereoOn: this.stereoOn, - actionMappingOn: this.actionMappingOn, - defaultPose: this.defaultPose, - deviceKey: this.deviceKey, - keyboardMappingOn: this.keyboardMappingOn, - roomDimension: this.roomDimension, - polyfillExcludes: Array.from(this.polyfillExcludes), - inputMode: this.inputMode, - handPoses: this.handPoses, - userObjects: this.userObjects, - triggerMode: this.triggerMode, - }); - return new Promise((resolve) => { - localStorage.set(settings, () => { - resolve(settings); - }); - }); - } - - clear() { - const settings = {}; - settings[STORAGE_KEY] = null; - return new Promise((resolve) => { - localStorage.set(settings, () => { - resolve(settings); - }); - }); - } -} diff --git a/devtool/js/joystick.js b/devtool/js/joystick.js deleted file mode 100644 index 0c1dac8..0000000 --- a/devtool/js/joystick.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { EventEmitter } from 'events'; - -const CIRCUMFERENCE = 2 * Math.PI; -const LINEWIDTH = 4; -const OUTER_STROKE_COLOR = '#e4e6eb'; -const INNER_FILL_COLOR = '#317BEF'; - -export class Joystick extends EventEmitter { - constructor(size, autoReturn, renderScale = 2) { - super(); - this._renderScale = renderScale; - this._autoReturn = autoReturn; - - const canvas = document.createElement('canvas'); - canvas.id = 'Joystick'; - canvas.width = size * renderScale; - canvas.height = size * renderScale; - this._radius = (size / 2) * renderScale; - canvas.style.width = 75; - canvas.style.height = 75; - this._canvas = canvas; - - this._pressed = false; - this._innerRadius = this._radius / 2; - this._outerRadius = this._innerRadius * 1.5; - this._maxStickDelta = this._outerRadius - this._innerRadius / 2; - - this._centerX = this._radius; - this._centerY = this._radius; - this._refX = 0; - this._refY = 0; - this._deltaX = 0; - this._deltaY = 0; - - canvas.addEventListener('mousedown', this._onMouseDown.bind(this), false); - document.addEventListener('mousemove', this._onMouseMove.bind(this), false); - document.addEventListener('mouseup', this._onMouseUp.bind(this), false); - - this._drawOuterCircle(); - this._drawInnerCircle(); - } - - _drawOuterCircle() { - const context = this._canvas.getContext('2d'); - context.imageSmoothingQuality = 'high'; - context.beginPath(); - context.arc( - this._centerX, - this._centerY, - this._outerRadius, - 0, - CIRCUMFERENCE, - false, - ); - - context.lineWidth = LINEWIDTH * this._renderScale; - context.strokeStyle = OUTER_STROKE_COLOR; - context.stroke(); - } - - _drawInnerCircle() { - const context = this._canvas.getContext('2d'); - context.beginPath(); - const deltaDistance = Math.sqrt( - this._deltaX * this._deltaX + this._deltaY * this._deltaY, - ); - const scaleFactor = deltaDistance / this._maxStickDelta; - if (scaleFactor > 1) { - this._deltaX /= scaleFactor; - this._deltaY /= scaleFactor; - } - context.arc( - this._deltaX + this._centerX, - this._deltaY + this._centerY, - this._innerRadius, - 0, - CIRCUMFERENCE, - false, - ); - - context.fillStyle = INNER_FILL_COLOR; - context.fill(); - context.lineWidth = 0; - } - - _onMouseDown(event) { - this._refX = event.pageX; - this._refY = event.pageY; - this._pressed = true; - this._dispatchEvent(); - } - - _onMouseUp(_event) { - const context = this._canvas.getContext('2d'); - this._pressed = false; - - if (this._autoReturn) { - this._deltaX = 0; - this._deltaY = 0; - } - - context.clearRect(0, 0, this._canvas.width, this._canvas.height); - - this._drawOuterCircle(); - this._drawInnerCircle(); - this._dispatchEvent(); - } - - _onMouseMove(event) { - if (this._pressed) { - const context = this._canvas.getContext('2d'); - this._deltaX = (event.pageX - this._refX) * this._renderScale; - this._deltaY = (event.pageY - this._refY) * this._renderScale; - - context.clearRect(0, 0, this._canvas.width, this._canvas.height); - - this._drawOuterCircle(); - this._drawInnerCircle(); - this._dispatchEvent(); - } - } - - _dispatchEvent() { - this.emit('joystickmove'); - } - - overrideMove(x, y) { - const context = this._canvas.getContext('2d'); - this._deltaX = x * this._maxStickDelta; - this._deltaY = y * this._maxStickDelta; - - context.clearRect(0, 0, this._canvas.width, this._canvas.height); - - this._drawOuterCircle(); - this._drawInnerCircle(); - this._dispatchEvent(); - } - - addToParent(parent) { - parent.appendChild(this._canvas); - } - - getX() { - return (100 * (this._deltaX / this._maxStickDelta)).toFixed() / 100; - } - - getY() { - return (100 * (this._deltaY / this._maxStickDelta)).toFixed() / 100; - } - - reset() { - this._deltaX = 0; - this._deltaY = 0; - this._onMouseUp(); - } - - get sticky() { - return !this._autoReturn; - } - - setSticky(sticky) { - this._autoReturn = !sticky; - this._onMouseUp(); - } -} diff --git a/devtool/js/keyboard.js b/devtool/js/keyboard.js deleted file mode 100644 index 6ac9fe8..0000000 --- a/devtool/js/keyboard.js +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { DEVICE, KEYBOARD_CONTROL_MAPPING, OBJECT_NAME } from './constants'; -import { EmulatorSettings, emulatorStates } from './emulatorStates'; - -import { relayKeyboardEvent } from './messenger'; - -const emulatedJoysticks = {}; -const JOYSTICKS = emulatorStates.joysticks; - -const resetEmulatedJoysticks = () => { - emulatedJoysticks.left = { - left: false, - right: false, - forward: false, - backward: false, - }; - emulatedJoysticks.right = { - left: false, - right: false, - forward: false, - backward: false, - }; -}; - -const getReservedKeyAction = (key) => { - let result = null; - Object.entries(KEYBOARD_CONTROL_MAPPING).forEach(([handKey, mapping]) => { - Object.entries(mapping).forEach(([action, mappedKey]) => { - if (mappedKey == key) { - result = [handKey, action]; - } - }); - }); - return result; -}; - -const onReservedKeyDown = (handKey, action) => { - switch (action) { - case 'joystickLeft': - emulatedJoysticks[handKey].left = true; - break; - case 'joystickRight': - emulatedJoysticks[handKey].right = true; - break; - case 'joystickForward': - emulatedJoysticks[handKey].forward = true; - break; - case 'joystickBackward': - emulatedJoysticks[handKey].backward = true; - break; - case 'trigger': - case 'grip': - emulatorStates.sliders[handKey][action].value = 100; - emulatorStates.buttons[handKey][action].disabled = true; - emulatorStates.sliders[handKey][action].onInputFunc(); - break; - default: - emulatorStates.buttons[handKey][action].click(); - } -}; - -const onReservedKeyUp = (handKey, action) => { - switch (action) { - case 'joystickLeft': - emulatedJoysticks[handKey].left = false; - break; - case 'joystickRight': - emulatedJoysticks[handKey].right = false; - break; - case 'joystickForward': - emulatedJoysticks[handKey].forward = false; - break; - case 'joystickBackward': - emulatedJoysticks[handKey].backward = false; - break; - case 'trigger': - case 'grip': - emulatorStates.sliders[handKey][action].value = 0; - emulatorStates.buttons[handKey][action].disabled = false; - emulatorStates.sliders[handKey][action].onInputFunc(); - break; - default: - emulatorStates.buttons[handKey][action].click(); - } -}; - -/** - * - * @param {KeyboardEvent} event - */ -const passThroughKeyboardEvent = (event) => { - const options = { - key: event.key, - code: event.code, - location: event.location, - repeat: event.repeat, - isComposing: event.isComposing, - ctrlKey: event.ctrlKey, - shiftKey: event.shiftKey, - altKey: event.altKey, - metaKey: event.metaKey, - }; - - relayKeyboardEvent(event.type, options); -}; - -const moveJoysticks = () => { - Object.entries(emulatedJoysticks).forEach(([handKey, directions]) => { - const deviceId = handKey == 'left' ? DEVICE.INPUT_LEFT : DEVICE.INPUT_RIGHT; - const deviceName = OBJECT_NAME[deviceId]; - if ( - directions.left || - directions.right || - directions.forward || - directions.backward - ) { - const axisX = directions.left ? -1 : 0 + directions.right ? 1 : 0; - const axisY = directions.forward ? -1 : 0 + directions.backward ? 1 : 0; - const normalizeScale = Math.sqrt(axisX * axisX + axisY * axisY); - - if (JOYSTICKS[deviceName]) { - JOYSTICKS[deviceName].overrideMove( - axisX / normalizeScale, - axisY / normalizeScale, - ); - } - } else { - if (JOYSTICKS[deviceName]) { - JOYSTICKS[deviceName].overrideMove(0, 0); - } - } - }); -}; - -export default function initKeyboardControl() { - resetEmulatedJoysticks(); - window.addEventListener('blur', resetEmulatedJoysticks); - - document.addEventListener( - 'keydown', - (event) => { - const result = getReservedKeyAction(event.key); - if (EmulatorSettings.instance.actionMappingOn && result) { - const [handKey, action] = result; - onReservedKeyDown(handKey, action); - moveJoysticks(); - } else { - passThroughKeyboardEvent(event); - } - }, - false, - ); - - document.addEventListener( - 'keyup', - (event) => { - const result = getReservedKeyAction(event.key); - if (result) { - const [handKey, action] = result; - onReservedKeyUp(handKey, action); - moveJoysticks(); - } else if (EmulatorSettings.instance.actionMappingOn) { - passThroughKeyboardEvent(event); - } - }, - false, - ); - - document.addEventListener( - 'keypress', - (event) => { - const result = getReservedKeyAction(event.key); - if (!result && EmulatorSettings.instance.actionMappingOn) { - passThroughKeyboardEvent(event); - } - }, - false, - ); -} diff --git a/devtool/js/messenger.js b/devtool/js/messenger.js deleted file mode 100644 index 397b0f0..0000000 --- a/devtool/js/messenger.js +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { WebXREventDispatcher } from '../../webxr-emulator/WebXREventDispatcher'; -import { CLIENT_ACTIONS, EMULATOR_ACTIONS } from './actions'; -import { DEVICE, HAND_STRINGS, OBJECT_NAME } from './constants'; -import { EmulatorSettings, emulatorStates } from './emulatorStates'; - -const tabId = 0// chrome.devtools.inspectedWindow.tabId; - -const connection = { - port: null, - connect: () => { - connection.port = chrome.runtime.connect(null, { name: 'iwe_devtool' }); - connection.port.onMessage.addListener((payload) => { - switch (payload.action) { - case CLIENT_ACTIONS.ENTER_IMMERSIVE: - emulatorStates.inImmersive = true; - emulatorStates.emulatedDevice?.forceEmitPose(); - break; - case CLIENT_ACTIONS.EXIT_IMMERSIVE: - emulatorStates.inImmersive = false; - break; - } - }); - connection.port.onDisconnect.addListener(connection.connect); - }, -}; - -const executeAction = (action, detail = {}) => { - const payload = { detail }; - payload.tabId = tabId; - payload.action = action; - payload.type = action - WebXREventDispatcher.instance.dispatchEvent(payload); - // try { - // connection.port.postMessage(payload); - // } catch (_e) { - // connection.connect(); - // connection.port.postMessage(payload); - // } -}; - -export const syncDevicePose = (event) => { - const { deviceKey, position, quaternion } = event; - if (deviceKey === DEVICE.HEADSET) { - executeAction(EMULATOR_ACTIONS.HEADSET_POSE_CHANGE, { - position, - quaternion, - }); - } else { - executeAction(EMULATOR_ACTIONS.CONTROLLER_POSE_CHANGE, { - objectName: OBJECT_NAME[deviceKey], - position, - quaternion, - }); - } -}; - -export const applyControllerButtonPressed = (key, buttonIndex, pressed) => { - executeAction(EMULATOR_ACTIONS.BUTTON_STATE_CHANGE, { - objectName: OBJECT_NAME[key], - buttonIndex, - pressed, - }); -}; - -export const applyControllerButtonChanged = ( - key, - buttonIndex, - pressed, - touched, - value, -) => { - executeAction(EMULATOR_ACTIONS.BUTTON_STATE_CHANGE, { - objectName: OBJECT_NAME[key], - buttonIndex, - pressed, - touched, - value, - }); -}; - -export const applyControllerAnalogValue = (key, axisIndex, value) => { - executeAction(EMULATOR_ACTIONS.ANALOG_VALUE_CHANGE, { - objectName: OBJECT_NAME[key], - axisIndex, - value, - }); -}; - -export const changeEmulatedDeviceType = (deviceDefinition) => { - executeAction(EMULATOR_ACTIONS.DEVICE_TYPE_CHANGE, { deviceDefinition }); -}; - -export const toggleStereoMode = (enabled) => { - executeAction(EMULATOR_ACTIONS.STEREO_TOGGLE, { enabled }); -}; - -export const relayKeyboardEvent = (eventType, eventOptions) => { - executeAction(EMULATOR_ACTIONS.KEYBOARD_EVENT, { - eventType, - eventOptions, - }); -}; - -export const notifyExitImmersive = () => { - executeAction(EMULATOR_ACTIONS.EXIT_IMMERSIVE); -}; - -export const changeRoomDimension = () => { - executeAction(EMULATOR_ACTIONS.ROOM_DIMENSION_CHANGE, { - dimension: EmulatorSettings.instance.roomDimension, - }); -}; - -export const changeInputMode = () => { - executeAction(EMULATOR_ACTIONS.INPUT_MODE_CHANGE, { - inputMode: EmulatorSettings.instance.inputMode, - }); -}; - -export const changeHandPose = (deviceId) => { - const handName = HAND_STRINGS[deviceId].name; - executeAction(EMULATOR_ACTIONS.HAND_POSE_CHANGE, { - handedness: deviceId === DEVICE.INPUT_LEFT ? 'left' : 'right', - pose: EmulatorSettings.instance.handPoses[handName], - }); -}; - -export const updatePinchValue = (deviceId) => { - const handName = HAND_STRINGS[deviceId].name; - executeAction(EMULATOR_ACTIONS.PINCH_VALUE_CHANGE, { - handedness: deviceId === DEVICE.INPUT_LEFT ? 'left' : 'right', - value: emulatorStates.pinchValues[handName], - }); -}; - -export const togglePolyfill = () => { - chrome.tabs.get(tabId, (tab) => { - const url = new URL(tab.url); - const urlMatchPattern = url.origin + '/*'; - if (EmulatorSettings.instance.polyfillExcludes.has(urlMatchPattern)) { - EmulatorSettings.instance.polyfillExcludes.delete(urlMatchPattern); - } else { - EmulatorSettings.instance.polyfillExcludes.add(urlMatchPattern); - } - EmulatorSettings.instance.write().then(() => { - executeAction(EMULATOR_ACTIONS.EXCLUDE_POLYFILL); - }); - }); -}; - -export const toggleControllerVisibility = (deviceKey, visible) => { - executeAction(EMULATOR_ACTIONS.CONTROLLER_VISIBILITY_CHANGE, { - objectName: OBJECT_NAME[deviceKey], - visible, - }); -}; - -export const toggleHandVisibility = (deviceId, visible) => { - executeAction(EMULATOR_ACTIONS.HAND_VISIBILITY_CHANGE, { - handedness: HAND_STRINGS[deviceId].handedness, - visible, - }); -}; - -export const reloadInspectedTab = () => { - executeAction(EMULATOR_ACTIONS.EXCLUDE_POLYFILL); -}; - -export const updateUserObjects = () => { - executeAction(EMULATOR_ACTIONS.USER_OBJECTS_CHANGE); -}; diff --git a/devtool/jsx/app.jsx b/devtool/jsx/app.jsx deleted file mode 100644 index 454e3fc..0000000 --- a/devtool/jsx/app.jsx +++ /dev/null @@ -1,123 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import ControllerPanel from './controllers.jsx'; -import { DEVICE } from '../js/constants'; -import { EmulatorSettings } from '../js/emulatorStates.js'; -import HandPanel from './hands.jsx'; -import HeadsetBar from './headset.jsx'; -import Inspector from './inspector.jsx'; -import PoseBar from './pose.jsx'; -import React from 'react'; - -const MIN_PANEL_WIDTH = 327; -const MIN_PANEL_HEIGHT = 256; -const MIN_INPUT_PANEL_WIDTH = 420; -const MIN_INPUT_PANEL_HEIGHTS = { - controllers: 452, - hands: 327, -}; - -export default function App({ device }) { - const [inputMode, setInputMode] = React.useState( - EmulatorSettings.instance.inputMode, - ); - const [showInspector, setShowInspector] = React.useState(true); - const [showControls, setShowControls] = React.useState(true); - const sizeWarningRef = React.useRef(); - React.useEffect(onResize, [inputMode]); - - React.useEffect(() => { - window.addEventListener('resize', function () { - onResize(); - }); - }); - - function onResize() { - const body = document.getElementById('devtools') - if (body.offsetHeight < MIN_PANEL_HEIGHT) { - if (showInspector) setShowInspector(false); - if (showControls) setShowControls(false); - sizeWarningRef.current.innerHTML = 'Not Enough Vertical Space'; - } else if (body.offsetWidth < MIN_PANEL_WIDTH) { - if (showInspector) setShowInspector(false); - if (showControls) setShowControls(false); - sizeWarningRef.current.innerHTML = 'Not Enough Horizontal Space'; - } else { - if (!showInspector) setShowInspector(true); - const inputMode = EmulatorSettings.instance.inputMode; - if (body.offsetWidth < MIN_INPUT_PANEL_WIDTH) { - if (showControls) setShowControls(false); - sizeWarningRef.current.innerHTML = 'Not Enough Horizontal Space'; - } else if ( - body.offsetHeight < MIN_INPUT_PANEL_HEIGHTS[inputMode] - ) { - if (showControls) setShowControls(false); - sizeWarningRef.current.innerHTML = 'Not Enough Vertical Space'; - } else { - if (!showControls) setShowControls(true); - } - } - device.render(); - } - - return ( - <> -
-
- -
-
- -
-
- -
-
-
-
- {[DEVICE.INPUT_LEFT, DEVICE.INPUT_RIGHT].map((deviceKey) => ( - - ))} -
-
- {[DEVICE.INPUT_LEFT, DEVICE.INPUT_RIGHT].map((deviceKey) => ( - - ))} -
-
- -
-
-
-
-
- - ); -} diff --git a/devtool/jsx/controllers.jsx b/devtool/jsx/controllers.jsx deleted file mode 100644 index 3e1a60d..0000000 --- a/devtool/jsx/controllers.jsx +++ /dev/null @@ -1,339 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - BUTTON_POLYFILL_INDEX_MAPPING, - CONTROLLER_STRINGS, - PRESS_AND_RELEASE_DURATION, - TRIGGER_CONFIG, -} from '../js/constants'; -import { EmulatorSettings, emulatorStates } from '../js/emulatorStates'; -import { - applyControllerAnalogValue, - applyControllerButtonChanged, - applyControllerButtonPressed, - toggleControllerVisibility, -} from '../js/messenger'; -import config from '@ir-engine/common/src/config'; - -const assetURL = config.client.fileServer + '/projects/ir-engine/ir-bot/devtool' - -import { Joystick } from '../js/joystick'; -import React from 'react'; - -function ControlButtonGroup({ isAnalog, deviceKey, buttonKey }) { - const touchRef = React.useRef(); - const pressRef = React.useRef(); - const holdRef = React.useRef(); - const rangeRef = React.useRef(); - const deviceName = CONTROLLER_STRINGS[deviceKey].name; - const buttonState = emulatorStates.controllers[deviceName][buttonKey]; - - function onTouchToggle() { - buttonState.touched = !buttonState.touched; - buttonState.touched ||= buttonState.pressed; - touchRef.current.classList.toggle('button-pressed', buttonState.touched); - applyControllerButtonChanged( - deviceKey, - BUTTON_POLYFILL_INDEX_MAPPING[buttonKey], - buttonState.pressed, - buttonState.touched, - buttonState.value, - ); - } - - function onHoldToggle() { - buttonState.pressed = !buttonState.pressed; - buttonState.touched ||= buttonState.pressed; - pressRef.current.disabled = buttonState.pressed; - holdRef.current.classList.toggle('button-pressed', buttonState.pressed); - applyControllerButtonPressed( - deviceKey, - BUTTON_POLYFILL_INDEX_MAPPING[buttonKey], - buttonState.pressed, - ); - } - - function onPressBinary() { - if (buttonState.pressed) return; - onHoldToggle(); - pressRef.current.disabled = true; - holdRef.current.disabled = true; - setTimeout(() => { - onHoldToggle(); - pressRef.current.disabled = false; - holdRef.current.disabled = false; - }, PRESS_AND_RELEASE_DURATION); - } - - function onRangeInput() { - const inputValue = rangeRef.current.value / 100; - applyControllerButtonChanged( - deviceKey, - BUTTON_POLYFILL_INDEX_MAPPING[buttonKey], - inputValue != 0, - buttonState.touched, - inputValue, - ); - } - - const onPressAnalog = createAnalogPressFunction( - pressRef, - rangeRef, - onRangeInput, - ); - - React.useEffect(() => { - const handedness = CONTROLLER_STRINGS[deviceKey].handedness; - if (isAnalog) { - rangeRef.current.value = 0; - onRangeInput(); - if (!emulatorStates.sliders[handedness]) { - emulatorStates.sliders[handedness] = {}; - } - rangeRef.current.onInputFunc = onRangeInput; - emulatorStates.sliders[handedness][buttonKey] = rangeRef.current; - } - if (!emulatorStates.buttons[handedness]) { - emulatorStates.buttons[handedness] = {}; - } - emulatorStates.buttons[handedness][buttonKey] = pressRef.current; - }); - - return ( -
- - - {isAnalog ? ( - - ) : ( - - )} -
- ); -} - -export default function ControllerPanel({ deviceKey, device }) { - const strings = CONTROLLER_STRINGS[deviceKey]; - const joystickContainerRef = React.useRef(); - const joystickResetRef = React.useRef(); - const joystickStickyRef = React.useRef(); - - const [showController, setShowController] = React.useState(true); - - const joystick = new Joystick(100, true, 1); - emulatorStates.joysticks[strings.name] = joystick; - joystick.on('joystickmove', () => { - // update joystick - applyControllerAnalogValue(deviceKey, 0, joystick.getX()); - applyControllerAnalogValue(deviceKey, 1, joystick.getY()); - - joystickResetRef.current.disabled = !( - joystick.sticky && - joystick.getX() != 0 && - joystick.getY() != 0 - ); - }); - - function onStickyToggle() { - joystick.setSticky(!joystick.sticky); - joystickStickyRef.current.classList.toggle( - 'button-pressed', - joystick.sticky, - ); - } - - function toggleDeviceVisibility(event) { - setShowController(!showController); // React state only applies to the next rendering frame - device.toggleControllerVisibility(deviceKey, !showController); - toggleControllerVisibility(deviceKey, !showController); - event.target.classList.toggle('button-pressed', showController); - } - - React.useEffect(() => { - joystick.addToParent(joystickContainerRef.current); - }, []); - - return ( -
-
-
-
-
- - {strings.displayName} -
- -
-
-
-
-
-
-
-
- -
-
-
-
-
- - -
-
-
- {['trigger', 'grip'].map((controlName) => ( -
-
- - {controlName} -
-
- -
-
- ))} - {['button2', 'button1'].map((controlName) => ( -
-
- - {strings[controlName]} -
-
- -
-
- ))} -
-
-
-
- ); -} - -export function createAnalogPressFunction(pressRef, rangeRef, onRangeInput) { - return function () { - const step = 10; - const { interval, holdTime } = - TRIGGER_CONFIG[EmulatorSettings.instance.triggerMode]; - pressRef.current.disabled = true; - let rangeValue = 0; - const pressIntervalId = setInterval(() => { - if (rangeRef.current.value >= 100) { - rangeRef.current.value = 100; - clearInterval(pressIntervalId); - setTimeout(() => { - const depressIntervalId = setInterval(() => { - if (rangeRef.current.value <= 0) { - rangeRef.current.value = 0; - clearInterval(depressIntervalId); - pressRef.current.disabled = false; - } else { - rangeRef.current.value -= step; - } - onRangeInput(); - }, interval); - }, holdTime); - } else { - rangeValue += step; - rangeRef.current.value = rangeValue; - } - onRangeInput(); - }, interval); - }; -} diff --git a/devtool/jsx/hands.jsx b/devtool/jsx/hands.jsx deleted file mode 100644 index af4e8dc..0000000 --- a/devtool/jsx/hands.jsx +++ /dev/null @@ -1,138 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { EmulatorSettings, emulatorStates } from '../js/emulatorStates'; -import { - changeHandPose, - updatePinchValue, - toggleHandVisibility, -} from '../js/messenger'; - -import { HAND_STRINGS } from '../js/constants'; -import React from 'react'; -import { createAnalogPressFunction } from './controllers.jsx'; - -import config from '@ir-engine/common/src/config'; -const assetURL = config.client.fileServer + '/projects/ir-engine/ir-bot/devtool' - - -export default function HandPanel({ deviceKey, device }) { - const strings = HAND_STRINGS[deviceKey]; - const pressRef = React.createRef(); - const rangeRef = React.createRef(); - const poseSelectRef = React.createRef(); - - const [showHand, setShowHand] = React.useState(true); - - function onHandPoseChange() { - EmulatorSettings.instance.handPoses[strings.name] = - poseSelectRef.current.value; - EmulatorSettings.instance.write(); - changeHandPose(deviceKey); - } - - function onRangeInput() { - emulatorStates.pinchValues[strings.name] = rangeRef.current.value / 100; - updatePinchValue(deviceKey); - } - - function toggleDeviceVisibility(event) { - setShowHand(!showHand); // React state only applies to the next rendering frame - device.toggleHandVisibility(deviceKey, !showHand); - toggleHandVisibility(deviceKey, !showHand); - event.target.classList.toggle('button-pressed', showHand); - } - - const onPressAnalog = createAnalogPressFunction( - pressRef, - rangeRef, - onRangeInput, - ); - - React.useEffect(onRangeInput, []); - - return ( -
-
-
-
-
- - {strings.displayName} -
- -
-
-
-
-
- Pose -
-
-
- -
-
-
-
-
- Pinch -
-
-
- - -
-
-
-
-
-
-
- ); -} diff --git a/devtool/jsx/headset.jsx b/devtool/jsx/headset.jsx deleted file mode 100644 index 9636d83..0000000 --- a/devtool/jsx/headset.jsx +++ /dev/null @@ -1,217 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { DEFAULT_TRANSFORMS, TRIGGER_MODES } from '../js/constants'; -import { - changeEmulatedDeviceType, - notifyExitImmersive, - reloadInspectedTab, - togglePolyfill, - toggleStereoMode, -} from '../js/messenger'; -import config from '@ir-engine/common/src/config'; - -const assetURL = config.client.fileServer + '/projects/ir-engine/ir-bot/devtool' - -import { DEVICE_DEFINITIONS } from '../js/devices'; -import { EmulatorSettings } from '../js/emulatorStates'; -import React from 'react'; - -export default function HeadsetBar({ device }) { - const headsetSelectRef = React.useRef(); - const polyfillToggleRef = React.useRef(); - const stereoToggleRef = React.useRef(); - const [polyfillOn, setPolyfillOn] = React.useState(true); - const [showDropDown, setShowDropDown] = React.useState(false); - const [triggerMode, setTriggerMode] = React.useState( - EmulatorSettings.instance.triggerMode, - ); - - function onChangeDevice() { - const deviceId = headsetSelectRef.current.value; - if (DEVICE_DEFINITIONS[deviceId]) { - EmulatorSettings.instance.deviceKey = deviceId; - changeEmulatedDeviceType(DEVICE_DEFINITIONS[deviceId]); - EmulatorSettings.instance.write(); - } - } - - function onToggleStereo() { - EmulatorSettings.instance.stereoOn = !EmulatorSettings.instance.stereoOn; - toggleStereoMode(EmulatorSettings.instance.stereoOn); - stereoToggleRef.current.classList.toggle( - 'button-pressed', - EmulatorSettings.instance.stereoOn, - ); - EmulatorSettings.instance.write(); - } - - const updatePolyfillState = (tab) => { - const url = new URL(tab.url); - const urlMatchPattern = url.origin + '/*'; - setPolyfillOn( - !EmulatorSettings.instance.polyfillExcludes.has(urlMatchPattern), - ); - }; - - React.useEffect(() => { - // check every time navigation happens on the tab - // chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { - // if ( - // tabId === chrome.devtools.inspectedWindow.tabId && - // changeInfo.status === 'complete' - // ) { - // updatePolyfillState(tab); - // } - // }); - - // // check on start up - // chrome.tabs.get(chrome.devtools.inspectedWindow.tabId, (tab) => { - // updatePolyfillState(tab); - // }); - }); - - return ( -
-
-
-
-
- - -
-
-
- - - - -
- {showDropDown && ( -
- - - - -
- )} -
-
-
-
- ); -} diff --git a/devtool/jsx/inspector.jsx b/devtool/jsx/inspector.jsx deleted file mode 100644 index 3932266..0000000 --- a/devtool/jsx/inspector.jsx +++ /dev/null @@ -1,381 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { - CONTROLLER_STRINGS, - DEVICE, - HAND_STRINGS, - OBJECT_NAME, - SEMANTIC_LABELS, -} from '../js/constants'; - -import { EmulatorSettings } from '../js/emulatorStates'; -import React from 'react'; -import { changeRoomDimension } from '../js/messenger'; - -import config from '@ir-engine/common/src/config'; -const assetURL = config.client.fileServer + '/projects/ir-engine/ir-bot/devtool' - -export default function Inspector({ device, inputMode }) { - const sceneContainerRef = React.useRef(); - // plane setting refs - const planeWidthRef = React.useRef(); - const planeHeightRef = React.useRef(); - const [planeVertical, setPlaneVertical] = React.useState(true); - const planeSemanticLabelRef = React.useRef(); - // mesh setting refs - const meshWidthRef = React.useRef(); - const meshHeightRef = React.useRef(); - const meshDepthRef = React.useRef(); - const meshSemanticLabelRef = React.useRef(); - - const [showTransforms, setShowTransforms] = React.useState(true); - const [showRoomSettings, setShowRoomSettings] = React.useState(false); - const [showPlaneSettings, setShowPlaneSettings] = React.useState(false); - const [showMeshSettings, setShowMeshSettings] = React.useState(false); - const transformData = {}; - Object.values(DEVICE).forEach((deviceKey) => { - const deviceName = OBJECT_NAME[deviceKey]; - transformData[deviceName] = React.useState({ - position: [0, 0, 0], - rotation: [0, 0, 0], - }); - }); - - const [inputValues, setInputValues] = React.useState( - Object.values(DEVICE).reduce((acc, deviceKey) => { - const deviceName = OBJECT_NAME[deviceKey]; - for (let i = 0; i < 3; i++) { - acc[`${deviceName}-position-${i}`] = - transformData[deviceName][0].position[i]; - acc[`${deviceName}-rotation-${i}`] = - transformData[deviceName][0].rotation[i]; - } - return acc; - }, {}), - ); - - function handleInputChange(key, event) { - const value = parseFloat(event.target.value); - if (!isNaN(value)) { - const clampedValue = roundAndClamp(value); - setInputValues((prevValues) => ({ - ...prevValues, - [key]: clampedValue, - })); - // Split the key into its components - const [deviceName, type, index] = key.split('-'); - const deviceKey = Object.keys(OBJECT_NAME).find( - (key) => OBJECT_NAME[key] === deviceName, - ); - // Update the device transform - if (deviceKey) { - const position = [...transformData[deviceName][0].position]; - const rotation = [...transformData[deviceName][0].rotation]; - if (type === 'position') { - position[index] = clampedValue; - } else if (type === 'rotation') { - rotation[index] = clampedValue; - } - device.setDeviceTransform(deviceKey, position, rotation); - } - } - } - - function roundAndClamp(number) { - const rounded = Math.round(number * 100) / 100; - return Math.min(Math.max(rounded, -99.99), 99.99); - } - - React.useEffect(() => { - sceneContainerRef.current.appendChild(device.canvas); - sceneContainerRef.current.appendChild(device.labels); - device.on('pose', (event) => { - const { deviceKey, position, rotation } = event; - const deviceName = OBJECT_NAME[deviceKey]; - const transform = transformData[deviceName]; - const setTransform = transform[1]; - setTransform({ position, rotation }); - setInputValues((prevValues) => ({ - ...prevValues, - [`${deviceName}-position-0`]: roundAndClamp(position[0]), - [`${deviceName}-position-1`]: roundAndClamp(position[1]), - [`${deviceName}-position-2`]: roundAndClamp(position[2]), - [`${deviceName}-rotation-0`]: roundAndClamp(rotation[0]), - [`${deviceName}-rotation-1`]: roundAndClamp(rotation[1]), - [`${deviceName}-rotation-2`]: roundAndClamp(rotation[2]), - })); - }); - device.forceEmitPose(); - }, []); - return ( - <> -
-
- - - - - {showTransforms && - Object.values(DEVICE).map((deviceKey) => { - const deviceName = OBJECT_NAME[deviceKey]; - return ( -
- -
- {['position', 'rotation'].map((type) => ( -
-
- {[0, 1, 2].map((i) => ( - - handleInputChange( - `${deviceName}-${type}-${i}`, - event, - ) - } - /> - ))} -
-
- ))} -
-
- ); - })}{' '} - {showRoomSettings && ( -
-
- -
-
- {[ - ['x', 'width'], - ['y', 'height'], - ['z', 'depth'], - ].map(([key, name]) => ( -
- Space {name}: - { - EmulatorSettings.instance.roomDimension[key] = parseFloat( - event.target.value, - ); - EmulatorSettings.instance.write(); - device.updateRoom(); - changeRoomDimension(); - }} - /> -
- ))} -
-
- )} - {showPlaneSettings && ( -
-
- - - -
-
- - -
-
- -
-
- -
-
- )} - {showMeshSettings && ( -
-
- - - -
-
- - -
-
- -
-
- -
-
- )} -
- - ); -} diff --git a/devtool/jsx/pose.jsx b/devtool/jsx/pose.jsx deleted file mode 100644 index 87bd12d..0000000 --- a/devtool/jsx/pose.jsx +++ /dev/null @@ -1,157 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import { EmulatorSettings, emulatorStates } from '../js/emulatorStates'; - -import { DEVICE } from '../js/constants'; -import React from 'react'; -import { changeInputMode } from '../js/messenger'; -import initKeyboardControl from '../js/keyboard'; - - -import config from '@ir-engine/common/src/config'; -const assetURL = config.client.fileServer + '/projects/ir-engine/ir-bot/devtool' - -export default function PoseBar({ device, setInputMode }) { - const saveDefaultPoseRef = React.useRef(); - const resetPoseRef = React.useRef(); - const actionMappingToggleRef = React.useRef(); - const handModeToggleRef = React.useRef(); - const controllerModeToggleRef = React.useRef(); - - function onSaveDefaultPose() { - const deviceTransform = {}; - Object.values(DEVICE).forEach((device) => { - deviceTransform[device] = {}; - deviceTransform[device].position = - emulatorStates.assetNodes[device].position.toArray(); - deviceTransform[device].rotation = - emulatorStates.assetNodes[device].rotation.toArray(); - }); - EmulatorSettings.instance.defaultPose = deviceTransform; - EmulatorSettings.instance.write(); - } - - function onActionMappingToggle() { - EmulatorSettings.instance.actionMappingOn = - !EmulatorSettings.instance.actionMappingOn; - actionMappingToggleRef.current.classList.toggle( - 'button-pressed', - EmulatorSettings.instance.actionMappingOn, - ); - EmulatorSettings.instance.write(); - } - - function onInputModeChange(inputMode) { - EmulatorSettings.instance.inputMode = inputMode; - EmulatorSettings.instance.write(); - changeInputMode(); - controllerModeToggleRef.current.classList.toggle( - 'button-pressed', - inputMode === 'controllers', - ); - handModeToggleRef.current.classList.toggle( - 'button-pressed', - inputMode === 'hands', - ); - setInputMode(inputMode); - } - - React.useEffect(() => { - changeInputMode(); - initKeyboardControl(); - }, []); - - return ( -
-
-
-
- -
- - -
-
- -
-
- - - -
-
-
-
-
- ); -} diff --git a/devtool/styles/index.css b/devtool/styles/index.css deleted file mode 100644 index 7536ec7..0000000 --- a/devtool/styles/index.css +++ /dev/null @@ -1,600 +0,0 @@ -/* - Copyright (c) Meta Platforms, Inc. and affiliates. - - This source code is licensed under the MIT license found in the - LICENSE file in the root directory of this source tree. -*/ - -.row { - margin: 0px; -} - -.row > * { - padding-left: 5px; - padding-right: 5px; -} - -.component { - display: flex; - flex-direction: column; -} - -.card { - border: 0px; -} - -.component-container { - width: 100%; - padding: 0; -} - -.root-panel { - border-radius: 10px; - color: #a7a7a7; -} - -.inspector-panel { - flex: 1 1 auto; - display: flex; - flex-direction: column; -} - -.controls-panel { - flex: 0 1 auto; -} - -.inspector-panel .row { - flex: 1 1 auto; -} - -.inspector-panel .row:first-child, -.inspector-panel .row:last-child { - flex: 0 1 auto; -} - -#scene-container { - padding: 0; - width: 100%; - height: 100%; - position: relative; - overflow: hidden; -} - -.headset-action-button { - height: 24px; - border-radius: 6px; - border-style: none; - padding: 4px 6px 4px 6px; - margin: 0px 0px 0px 10px; - font-size: small; - background-color: #3b3b3c; - color: #e4e6eb; - text-align: center; -} - -.headset-select { - height: 2.1em; - width: 70px; - appearance: none; - border-radius: 6px; - border-style: none; - padding: 4px; - margin: 0px 0px 0px 10px; - font-size: small; - background-color: #252526; - color: #e4e6eb; - text-align: center; -} - -.headset-select:hover { - background-color: #3b3b3c; -} - -.headset-select:before { - content: 'Device: '; - color: #e4e6eb; -} - -.special-button { - height: 2.1em; - border-radius: 6px; - padding: 4px; - margin: 0px 0px 0px 10px; - font-size: small; - background-color: #3b3b3c; - color: #e4e6eb; -} - -.special-button img { - width: 1.2em; -} - -.pose-action-button { - height: 2em; - display: inline-block; - border-radius: 6px; - border-style: none; - padding: 2px 6px; - margin: 0px; - font-size: small; - background-color: #3b3b3c; - color: #e4e6eb; - text-align: center; - vertical-align: middle; -} - -.pose-action-button:hover, -.headset-action-button:hover, -.special-button:hover { - background-color: #676767; - color: #e4e6eb; -} - -.pose-action-button:disabled, -.special-button:disabled { - background-color: #2a2a2b; - /* color: #656568; */ - color: #b0b0b0; -} - -.button-pressed { - background-color: #317bef; -} - -.button-pressed:hover { - background-color: #679bec; -} - -.button-rightmost { - border-radius: 2px 6px 6px 2px !important; - margin-left: 0px; -} - -.button-leftmost { - border-radius: 6px 2px 2px 6px !important; -} - -.button-middle { - border-radius: 2px !important; - margin-left: 0px; -} - -.joystick-button { - top: 0px; - right: 12px; - position: absolute; - width: 70px; - margin: 2px; -} - -.action-icon { - width: 1.25em; - height: 1.25em; -} - -.control-icon { - width: 2em; - height: 2em; -} - -.control-label { - text-transform: capitalize; - padding-left: 5px; -} - -.control-button-group { - margin-left: 5px; -} - -.control-button-group * { - border-radius: 2px !important; - margin: 0px 2px 0px 0px; -} - -.control-button-group *:first-child { - border-radius: 6px 2px 2px 6px !important; -} - -.control-button-group *:last-child { - border-radius: 2px 6px 6px 2px !important; - margin: 0px; -} - -.controller-card { - border-radius: 10px; - background-color: #252526; - color: #a7a7a7; -} - -.headset-card { - border-radius: 10px 10px 0 0; - background-color: #252526; - color: #a7a7a7; - margin: 5px 5px 0px 5px; -} - -.headset-card .card-body, -.pose-card .card-body { - padding: 5px 0px 5px 0px; -} - -.headset-card .row > *, -.pose-card .row > * { - padding-right: 0px; -} - -.controller-panel { - width: 100%; - padding: 5px; -} - -.controller-panel .row { - margin: 4px 0px !important; -} - -.controller-panel .col { - padding: 0; -} - -.controller-panel .col:first-child { - margin-right: 2.5px; -} - -.controller-panel .col:last-child { - margin-left: 2.5px; -} - -.controller-card .card-header { - border-bottom-width: 2px; - padding: 2px 5px; -} - -.controller-card .card-body { - padding: 0 0 1px 0; -} - -.joystick-panel { - width: 80px; - position: relative; - top: 30px; - left: 0px; -} - -#render-component { - padding-left: 5px; - padding-right: 5px; - width: 100%; -} - -#transform-component { - position: absolute; - padding: 0; - pointer-events: none; -} - -#transform-component button { - border: none; - pointer-events: all; -} - -#transform-component > button { - background-color: #252526; - color: white; - opacity: 50%; - height: 18px; - padding: 0px; - width: 50px; - border-radius: 4px; - margin: 2px 0px 1px 2px; -} - -#transform-component button:hover { - opacity: 1; -} - -#transform-component button.active { - font-weight: bold; - opacity: 1; -} - -#transform-component input { - pointer-events: all; -} - -.transform-card { - color: white; - margin: 2px !important; - width: 155px; - font-size: 10px; - line-height: 1.2em; -} - -.transform-icon { - background-color: #252526; - opacity: 50%; - border-radius: 6px 2px 2px 6px; -} - -.transform-body { - background-color: #252526; - width: 127px; - opacity: 50%; - color: white; - border-radius: 2px 6px 6px 2px; - padding: 0px; - margin-left: 2px; -} - -.transform-body .row { - margin: 1%; -} - -.value { - font-family: 'Roboto Mono', monospace; - padding: 0; -} - -.value > input[type='number'] { - border: none; - background-color: black; - border-radius: 5px; - width: 40px; - -webkit-appearance: none; - -moz-appearance: textfield; - appearance: textfield; - margin-left: 2px; - color: white; - text-align: center; -} - -.value > input[type='number']:first-child { - margin-left: 0; -} - -.value > input[type='number']:focus { - outline: none; -} - -.value-changed { - color: #679bec; -} - -.form-range { - max-width: 50px; - vertical-align: middle; -} - -.form-range:hover { - background-color: #3b3b3c; -} - -.form-range::-webkit-slider-runnable-track { - height: 1.4em; - border-radius: 5px; -} - -.form-range::-webkit-slider-thumb { - -webkit-appearance: none; /* Override default look */ - appearance: none; - margin-top: -0.35em; /* Centers thumb on the track */ - border-radius: 2px; - height: 2.1em; - width: 8px; -} - -.pose-left-full { - width: 110px; -} - -.pose-right-full { - width: 85px; - margin-left: 3px; -} - -.pose-left-long { - width: 78px; - border-radius: 6px 2px 2px 6px; -} - -.pose-left-short { - width: 30px; - margin-left: 2px; - border-radius: 2px 6px 6px 2px; -} - -.pose-card { - border-radius: 0 0 10px 10px; - background-color: #252526; - color: #a7a7a7; - margin: 0px 5px 0px 5px; -} - -#mask { - background-color: #3b3b3c; - color: #e4e6eb; - position: absolute; - width: 100%; - height: 100%; - z-index: 1000; -} - -.prompt { - width: calc(100% - 10px); - height: calc(100% - 10px); - display: flex; - justify-content: center; - align-items: center; - margin: 5px; - border-radius: 3px; - border-width: 3px; - border-style: dashed; -} - -.alternative-control-card { - border-radius: 10px; - background-color: #252526; - color: #a7a7a7; -} - -.alternative-control-card .card-body { - padding: 5px 0px 5px 0px; -} - -.resize-warning-card { - background-color: #252526; - color: #a7a7a7; - align-items: center; - margin: 5px 5px 0px 5px; - border: 3px dashed; - border-bottom: none; - border-radius: 10px 10px 0px 0px; - height: 100%; - justify-content: center; -} - -.session-progress-bar { - display: inline-block; - width: calc(100% - 32px - 6em); - margin: 10px 5px 0px 5px; -} - -#session-file { - margin: 8px 0px 0px 16px; -} - -#save-default-pose { - margin-left: 10px; -} - -.playback-action-button { - height: 2.1em; - width: 2.1em; - border-radius: 6px; - border-style: none; - padding: 4px 6px 4px 6px; - font-size: small; - background-color: #3b3b3c; - color: #e4e6eb; - text-align: center; -} - -#playback-control { - margin: 5px 0px 0px 16px; -} - -#room-dimension-settings span { - height: 20px; - padding-left: 5px; -} - -#room-dimension-settings input { - width: 35px; - height: 15px; - padding: 1px 5px; - position: absolute; - border-radius: 8px; - border: 0px; - left: 100px; - background-color: #a7a7a7; -} - -#room-dimension-settings .row { - height: 15px; -} - -.controller-card .form-select { - width: 105px; -} - -.controller-card .form-select:focus { - color: #fff; - background-color: #3b3b3c; -} - -.controller-card .card-header .title { - padding-bottom: auto; -} - -.controller-card .card-body .row { - margin: 2%; -} - -.hand-card .form-range { - max-width: 78px; -} - -.component-container.row { - width: calc(100% - 10px); -} - -.drop-down-container { - background-color: #3b3b3c; - position: absolute; - width: 126px; - height: auto; - top: 41px; - right: 5px; - border-radius: 10px 2px 10px 10px; - z-index: 1; - padding: 5px; -} - -.drop-down-container button { - width: 100%; - margin: 0; - text-align: start; -} - -.mesh-menu { - margin: 5px; - width: 141px; -} - -.mesh-menu > div { - margin-bottom: 2px; -} - -input::-webkit-outer-spin-button, -input::-webkit-inner-spin-button { - -webkit-appearance: none; - margin: 0; -} - -.mesh-menu input[type='number'], -.mesh-menu select, -.mesh-menu button { - height: 18px; - border: none; - border-radius: 2px; - margin-right: 2px; - pointer-events: all; - text-align: center; - background-color: #252526; - color: white; - opacity: 50%; - vertical-align: middle; -} - -.mesh-menu input[type='number'], -.mesh-menu button { - appearance: none; - -webkit-appearance: none; - width: 40px; - padding: 0; -} - -.mesh-menu select { - width: 82px; -} - -.semantic-label { - position: absolute; - color: white; - pointer-events: none; - z-index: 20; - width: 50px; - height: 20px; - text-align: center; - margin-left: -25px; - margin-top: -10px; -} diff --git a/package.json b/package.json index 3392534..2301aad 100755 --- a/package.json +++ b/package.json @@ -32,9 +32,7 @@ "koa": "^2.14.2", "koa-bodyparser": "^4.4.0", "koa-router": "^12.0.0", - "localforage": "^1.10.0", - "puppeteer": "^19.6.3", - "webxr-polyfill": "github:felixtrz/webxr-polyfill#81d2db4f01b518f2c42b74a90973dac095515e9f" + "puppeteer": "^19.6.3" }, "license": "ISC", "devDependencies": { diff --git a/tests/xr.test.ts b/tests/xr.test.ts deleted file mode 100644 index 2f1956e..0000000 --- a/tests/xr.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { mockEngineRenderer } from '@ir-engine/spatial/tests/util/MockEngineRenderer' -import { createEngine, destroyEngine } from '@ir-engine/ecs/src/Engine' -import { - destroySpatialEngine, - destroySpatialViewer, - initializeSpatialEngine, - initializeSpatialViewer -} from '@ir-engine/spatial/src/initializeEngine' -import { requestXRSession } from '@ir-engine/spatial/src/xr/XRSessionFunctions' -import { describe, it, beforeEach, afterEach, assert, beforeAll } from 'vitest' -import { WebXREventDispatcher } from '../webxr-emulator/WebXREventDispatcher' -import { POLYFILL_ACTIONS } from '../webxr-emulator/actions' -import { getMutableState, getState } from '@ir-engine/hyperflux' -import { XRState } from '@ir-engine/spatial/src/xr/XRState' -import { EngineState } from '@ir-engine/spatial/src/EngineState' -import { RendererComponent } from '@ir-engine/spatial/src/renderer/WebGLRendererSystem' -import { ECSState, Timer, setComponent } from '@ir-engine/ecs' - -const deviceDefinition = { - id: 'Oculus Quest', - name: 'Oculus Quest', - modes: ['inline', 'immersive-vr'], - headset: { - hasPosition: true, - hasRotation: true - }, - controllers: [ - { - id: 'Oculus Touch (Right)', - buttonNum: 7, - primaryButtonIndex: 0, - primarySqueezeButtonIndex: 1, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - isComplex: true - }, - { - id: 'Oculus Touch (Left)', - buttonNum: 7, - primaryButtonIndex: 0, - primarySqueezeButtonIndex: 1, - hasPosition: true, - hasRotation: true, - hasSqueezeButton: true, - isComplex: true - } - ] -} - -describe.skip('WebXR', () => { - beforeAll(async () => { - const { CustomWebXRPolyfill } = await import('../webxr-emulator/CustomWebXRPolyfill') - new CustomWebXRPolyfill() - }) - - beforeEach(async () => { - createEngine() - initializeSpatialEngine() - initializeSpatialViewer() - - const timer = Timer((_time, xrFrame) => { - getMutableState(XRState).xrFrame.set(xrFrame) - // executeSystems(time) - getMutableState(XRState).xrFrame.set(null) - }) - getMutableState(ECSState).timer.set(timer) - - const { originEntity, localFloorEntity, viewerEntity } = getState(EngineState) - mockEngineRenderer(viewerEntity) - setComponent(viewerEntity, RendererComponent, { scenes: [originEntity, localFloorEntity, viewerEntity] }) - }) - - afterEach(async () => { - destroySpatialViewer() - destroySpatialEngine() - return await destroyEngine() - }) - - it('can define and initialize a device', async () => { - WebXREventDispatcher.instance.dispatchEvent({ - type: POLYFILL_ACTIONS.DEVICE_INIT, - detail: { stereoEffect: false, deviceDefinition } - }) - - await requestXRSession() - - assert(getState(XRState).session) - }) -}) diff --git a/webxr-emulator/CustomWebXRPolyfill.js b/webxr-emulator/CustomWebXRPolyfill.js deleted file mode 100644 index f1ffbbc..0000000 --- a/webxr-emulator/CustomWebXRPolyfill.js +++ /dev/null @@ -1,621 +0,0 @@ -import { - XRAnchor, - XRAnchorSet, - deletePersistentAnchor, - restorePersistentAnchors, -} from './api/XRAnchor'; -import XRFrame, { - PRIVATE as XRFRAME_PRIVATE, -} from 'webxr-polyfill/src/api/XRFrame'; -import XRSession, { - PRIVATE as XRSESSION_PRIVATE, -} from 'webxr-polyfill/src/api/XRSession'; -import { mat4, quat, vec3 } from 'gl-matrix'; - -import API from 'webxr-polyfill/src/api/index'; -import EX_API from './api/index'; -import EmulatedXRDevice from './EmulatedXRDevice'; -import { HAND_POSES } from './api/handPose'; -import { POLYFILL_ACTIONS } from './actions'; -import WebXRPolyfill from 'webxr-polyfill/src/WebXRPolyfill'; -import { PRIVATE as XRHAND_PRIVATE } from './api/XRHand'; -import XRHitTestResult from './api/XRHitTestResult'; -import XRHitTestSource from './api/XRHitTestSource'; -import { PRIVATE as XRJOINTSPACE_PRIVATE } from './api/XRJointSpace'; -import { XRJointPose } from './api/XRJointPose'; -import { XRMeshSet } from './api/XRMesh'; -import { XRPlaneSet } from './api/XRPlane'; -import XRReferenceSpace from 'webxr-polyfill/src/api/XRReferenceSpace'; -import XRRigidTransform from 'webxr-polyfill/src/api/XRRigidTransform'; -import XRSpace from 'webxr-polyfill/src/api/XRSpace'; -import XRSystem from 'webxr-polyfill/src/api/XRSystem'; -import XRTransientInputHitTestResult from './api/XRTransientInputHitTestResult'; -import XRTransientInputHitTestSource from './api/XRTransientInputHitTestSource'; -import { XR_COMPATIBLE } from 'webxr-polyfill/src/constants'; -import { WebXREventDispatcher } from './WebXREventDispatcher'; - -const handMatrixInvert = [1, -1, -1, 0, -1, 1, 1, 0, -1, 1, 1, 0, -1, 1, 1, 1]; -const getJointMatrix = (handPose, jointName, handedness) => { - const rawTransform = [...handPose[jointName].transform]; - if (handedness == 'right') { - for (let i = 0; i < 16; i++) { - rawTransform[i] = rawTransform[i] * handMatrixInvert[i]; - } - } - return mat4.fromValues(...rawTransform); -}; -const interpolateMatrix = (fromMatrix, toMatrix, alpha) => { - const fromPosition = vec3.create(); - mat4.getTranslation(fromPosition, fromMatrix); - const fromQuaternion = quat.create(); - mat4.getRotation(fromQuaternion, fromMatrix); - const fromScale = vec3.create(); - mat4.getScaling(fromScale, fromMatrix); - const toPosition = vec3.create(); - mat4.getTranslation(toPosition, toMatrix); - const toQuaternion = quat.create(); - mat4.getRotation(toQuaternion, toMatrix); - const toScale = vec3.create(); - mat4.getScaling(toScale, toMatrix); - const interpolatedPosition = vec3.create(); - vec3.lerp(interpolatedPosition, fromPosition, toPosition, alpha); - const interpolatedQuaternion = quat.create(); - quat.slerp(interpolatedQuaternion, fromQuaternion, toQuaternion, alpha); - const interpolatedScale = vec3.create(); - vec3.lerp(interpolatedScale, fromScale, toScale, alpha); - const out = mat4.create(); - mat4.fromRotationTranslationScale( - out, - interpolatedQuaternion, - interpolatedPosition, - interpolatedScale, - ); - return out; -}; - -export class CustomWebXRPolyfill extends WebXRPolyfill { - constructor() { - super(); - - // Note: Experimental. - // Override some XR APIs to track active immersive session to - // enable to exit immersive by the extension. - // Exiting without user gesture in the page might violate security policy - // so there might be a chance that we remove this feature at some point. - - let activeImmersiveSession = null; - const originalRequestSession = XRSystem.prototype.requestSession; - XRSystem.prototype.requestSession = function (mode, enabledFeatures = {}) { - return originalRequestSession - .call(this, mode, enabledFeatures) - .then((session) => { - if (mode === 'immersive-vr' || mode === 'immersive-ar') { - activeImmersiveSession = session; - - session.interactionMode = 'world-space' - - // DOM-Overlay API - const optionalFeatures = enabledFeatures.optionalFeatures; - const domOverlay = enabledFeatures.domOverlay; - if ( - optionalFeatures && - optionalFeatures.includes('dom-overlay') && - domOverlay && - domOverlay.root - ) { - const device = session[XRSESSION_PRIVATE].device; - device.setDomOverlayRoot(domOverlay.root); - session.domOverlayState = { type: 'screen' }; - } - - restorePersistentAnchors(session); - } - return session; - }); - }; - - const originalEnd = XRSession.prototype.end; - XRSession.prototype.end = function () { - return originalEnd.call(this).then(() => { - if (activeImmersiveSession === this) { - activeImmersiveSession = null; - } - }); - }; - - // add event listener for onreset event, but do nothing since we cannot re-center in emulator - XRReferenceSpace.prototype.addEventListener = () => { - // do nothing - }; - - WebXREventDispatcher.instance.addEventListener(POLYFILL_ACTIONS.EXIT_IMMERSIVE, () => { - if (activeImmersiveSession && !activeImmersiveSession.ended) { - activeImmersiveSession.end().then(() => { - activeImmersiveSession = null; - }); - } - }); - - XRSession.prototype.addTrackedAnchor = function (anchor) { - if (this.trackedAnchors == null) this.trackedAnchors = new Set(); - this.trackedAnchors.add(anchor); - }; - - XRSession.prototype.getTrackedAnchors = function () { - return this.trackedAnchors; - }; - - XRSession.prototype.hasTrackedAnchor = function (anchor) { - return this.trackedAnchors.has(anchor); - }; - - XRSession.prototype.deleteTrackedAnchor = function (anchor) { - if (this.trackedAnchors != null) { - this.trackedAnchors.delete(anchor); - } - }; - - XRSession.prototype.addPersistentAnchor = function (uuid, anchor) { - if (this.persistentAnchorsMap == null) { - this.persistentAnchorsMap = new Map(); - } - this.persistentAnchorsMap.set(uuid, anchor); - }; - - XRSession.prototype.getPersistentAnchorUUID = function (anchor) { - if (this.persistentAnchorsMap != null) { - let uuid; - this.persistentAnchorsMap.forEach((value, key) => { - if (value === anchor) { - uuid = key; - } - }); - return uuid; - } - }; - - Object.defineProperty(XRSession.prototype, 'persistentAnchors', { - get: function () { - const device = this[XRSESSION_PRIVATE].device; - const session = device.sessions.get(this[XRSESSION_PRIVATE].id); - if (!session.enabledFeatures.has('anchors')) { - return []; - } else if (this.persistentAnchorsMap != null) { - return Object.freeze(Array.from(this.persistentAnchorsMap.keys())); - } else { - return []; - } - }, - }); - - XRSession.prototype.restorePersistentAnchor = async function (uuid) { - const device = this[XRSESSION_PRIVATE].device; - const session = device.sessions.get(this[XRSESSION_PRIVATE].id); - if (!session.enabledFeatures.has('anchors')) { - return Promise.reject( - new DOMException( - "Failed to execute 'restorePersistentAnchor' on 'XRFrame': Anchors feature is not supported by the session.", - 'NotSupportedError', - ), - ); - } - if (!this.persistentAnchors.includes(uuid)) { - throw new DOMException( - 'specified persistent anchor cannot be found', - 'InvalidStateError', - ); - } - if (this[XRSESSION_PRIVATE].ended) { - throw new DOMException('session ended', 'InvalidStateError'); - } - - const anchor = this.persistentAnchorsMap.get(uuid); - this.addTrackedAnchor(anchor); - this.addPersistentAnchor(uuid, anchor); - return anchor; - }; - - XRSession.prototype.deletePersistentAnchor = async function (uuid) { - const device = this[XRSESSION_PRIVATE].device; - const session = device.sessions.get(this[XRSESSION_PRIVATE].id); - if (!session.enabledFeatures.has('anchors')) { - return Promise.reject( - new DOMException( - "Failed to execute 'deletePersistentAnchor' on 'XRFrame': Anchors feature is not supported by the session.", - 'NotSupportedError', - ), - ); - } - if (!this.persistentAnchors.includes(uuid)) { - throw new DOMException( - 'specified persistent anchor cannot be found', - 'InvalidStateError', - ); - } - - const anchor = this.persistentAnchorsMap.get(uuid); - this.deleteTrackedAnchor(anchor); - this.persistentAnchorsMap.delete(uuid); - await deletePersistentAnchor(uuid); - }; - - XRSession.prototype.updateTargetFrameRate = function (frameRate) { - console.log('now targeting', frameRate, 'fps'); - }; - - /** - * @param {import('webxr-polyfill/src/api/XRRigidTransform').default} pose - * @param {import('webxr-polyfill/src/api/XRSpace').default} space - * @see https://immersive-web.github.io/anchors/#dom-xrframe-createanchor - */ - XRFrame.prototype.createAnchor = async function (pose, space) { - const session = this[XRFRAME_PRIVATE].session; - const device = this[XRFRAME_PRIVATE].device; - const xrSession = device.sessions.get(session[XRSESSION_PRIVATE].id); - if (!xrSession.enabledFeatures.has('anchors')) { - return Promise.reject( - new DOMException( - "Failed to execute 'createAnchor' on 'XRFrame': Anchors feature is not supported by the session.", - 'NotSupportedError', - ), - ); - } - const localRefSpace = await session.requestReferenceSpace('local'); - - let currentSpaceTransform = null; - if ( - space._specialType === 'target-ray' || - space._specialType === 'grip' - ) { - currentSpaceTransform = device.getInputPose( - space._inputSource, - localRefSpace, - space._specialType, - ).transform; - } else { - space._ensurePoseUpdated(device, this[XRFRAME_PRIVATE].id); - localRefSpace._ensurePoseUpdated(device, this[XRFRAME_PRIVATE].id); - currentSpaceTransform = localRefSpace._getSpaceRelativeTransform(space); - } - if (!currentSpaceTransform) throw 'error creating anchor'; - - const currentSpaceBaseSpaceMatrix = new Float32Array(16); - mat4.multiply( - currentSpaceBaseSpaceMatrix, - localRefSpace._baseMatrix, - currentSpaceTransform.matrix, - ); - const anchorSpaceBaseSpaceMatrix = new Float32Array(16); - mat4.multiply( - anchorSpaceBaseSpaceMatrix, - currentSpaceBaseSpaceMatrix, - pose.matrix, - ); - const anchorSpace = new XRSpace(); - anchorSpace._baseMatrix = anchorSpaceBaseSpaceMatrix; - const anchor = new XRAnchor(session, anchorSpace); - session.addTrackedAnchor(anchor); - return anchor; - }; - - Object.defineProperty(XRFrame.prototype, 'trackedAnchors', { - get: function () { - const xrSession = this[XRFRAME_PRIVATE].session; - const device = this[XRFRAME_PRIVATE].device; - const session = device.sessions.get(xrSession[XRSESSION_PRIVATE].id); - if (!session.enabledFeatures.has('anchors')) { - return new XRAnchorSet(); - } else { - return new XRAnchorSet(xrSession.getTrackedAnchors()); - } - }, - }); - - Object.defineProperty(XRFrame.prototype, 'detectedPlanes', { - get: function () { - const device = this[XRFRAME_PRIVATE].device; - return new XRPlaneSet(device.xrScene.xrPlanes); - }, - }); - - Object.defineProperty(XRFrame.prototype, 'detectedMeshes', { - get: function () { - const device = this[XRFRAME_PRIVATE].device; - return new XRMeshSet(device.xrScene.xrMeshes); - }, - }); - - XRFrame.prototype.getJointPose = function (joint, baseSpace) { - const xrhand = joint[XRJOINTSPACE_PRIVATE].xrhand; - const xrInputSource = xrhand[XRHAND_PRIVATE].inputSource; - const handedness = xrInputSource.handedness; - const device = this[XRFRAME_PRIVATE].device; - const poseId = device.handPoseData[handedness].poseId; - const pinchValue = device.handPoseData[handedness].pinchValue; - const handPose = HAND_POSES[poseId]; - const pinchPose = HAND_POSES.pinch; - - // the joints transforms are sampled with gripSpace as the reference space - const gripMatrix = new Float32Array(16); - if (baseSpace == xrInputSource.gripSpace) { - mat4.fromRotationTranslation(gripMatrix, quat.create(), vec3.create()); - } else { - const gripPose = this.getPose(xrInputSource.gripSpace, baseSpace); - mat4.copy(gripMatrix, gripPose.transform.matrix); - } - - const jointMatrix = getJointMatrix(handPose, joint.jointName, handedness); - const pinchMatrix = getJointMatrix( - pinchPose, - joint.jointName, - handedness, - ); - const isPinchJoint = - joint.jointName.startsWith('thumb') || - joint.jointName.startsWith('index'); - const interpolatedMatrix = interpolateMatrix( - jointMatrix, - pinchMatrix, - isPinchJoint ? pinchValue : 0, - ); - - const out = new Float32Array(16); - mat4.multiply(out, gripMatrix, interpolatedMatrix); - return new XRJointPose( - new XRRigidTransform(out), - handPose[joint.jointName].radius, - ); - }; - - /** - * @param {IterableIterator} jointSpaces - * @param {Float32Array} radii - */ - XRFrame.prototype.fillJointRadii = function (jointSpaces, radii) { - const spaces = Array.from(jointSpaces); - if (spaces.length > radii.length) { - throw new TypeError('radii array size insufficient'); - } - spaces.forEach((jointSpace, i) => { - const xrhand = jointSpace[XRJOINTSPACE_PRIVATE].xrhand; - const xrInputSource = xrhand[XRHAND_PRIVATE].inputSource; - const handedness = xrInputSource.handedness; - const device = this[XRFRAME_PRIVATE].device; - const poseId = device.handPoseData[handedness].poseId; - const handPose = HAND_POSES[poseId]; - const jointName = jointSpace.jointName; - radii[i] = handPose[jointName].radius; - }); - return true; - }; - - /** - * @param {IterableIterator} spaces - * @param {XRSpace} baseSpace - * @param {Float32Array} transforms - */ - XRFrame.prototype.fillPoses = function (spaces, baseSpace, transforms) { - const spacesArray = Array.from(spaces); - if (transforms.length < spaces.length * 16) { - throw new TypeError('transforms array size insufficient'); - } - spacesArray.forEach((space, i) => { - let pose; - if (space.jointName) { - pose = this.getJointPose(space, baseSpace); - } else { - pose = this.getPose(space, baseSpace); - } - for (let j = 0; j < 16; j++) { - transforms[i * 16 + j] = pose.transform.matrix[j]; - } - }); - return true; - }; - - // Extending XRSession and XRFrame for AR hitting test API. - - XRSession.prototype.requestHitTestSource = function (options) { - const device = this[XRSESSION_PRIVATE].device; - const session = device.sessions.get(this[XRSESSION_PRIVATE].id); - if (!session.enabledFeatures.has('hit-test')) { - return Promise.reject( - new DOMException( - 'hit-test feature not requested or not supported', - 'NotSupportedError', - ), - ); - } - const source = new XRHitTestSource(this, options); - device.addHitTestSource(source); - return Promise.resolve(source); - }; - - XRSession.prototype.requestHitTestSourceForTransientInput = function ( - options, - ) { - const device = this[XRSESSION_PRIVATE].device; - const session = device.sessions.get(this[XRSESSION_PRIVATE].id); - if (!session.enabledFeatures.has('hit-test')) { - return Promise.reject( - new DOMException( - 'hit-test feature not requested or not supported', - 'NotSupportedError', - ), - ); - } - const source = new XRTransientInputHitTestSource(this, options); - device.addHitTestSourceForTransientInput(source); - return Promise.resolve(source); - }; - - Object.defineProperty(XRSession.prototype, 'enabledFeatures', { - get: function () { - const device = this[XRSESSION_PRIVATE].device; - const session = device.sessions.get(this[XRSESSION_PRIVATE].id); - return Object.freeze(Array.from(session.enabledFeatures)); - }, - }); - - Object.defineProperty(XRSession.prototype, 'inputSources', { - get: function () { - const device = this[XRSESSION_PRIVATE].device; - const inputSources = device.getInputSources(); - const session = device.sessions.get(this[XRSESSION_PRIVATE].id); - if (!session.enabledFeatures.has('hand-tracking')) { - return inputSources.filter((inputSource) => inputSource.hand == null); - } - return inputSources; - }, - }); - - XRFrame.prototype.getHitTestResults = function (hitTestSource) { - const device = this.session[XRSESSION_PRIVATE].device; - const hitTestResults = device.getHitTestResults(hitTestSource); - const results = []; - for (const matrix of hitTestResults) { - results.push(new XRHitTestResult(this, new XRRigidTransform(matrix))); - } - return results; - }; - - XRFrame.prototype.getHitTestResultsForTransientInput = function ( - hitTestSource, - ) { - const device = this.session[XRSESSION_PRIVATE].device; - const hitTestResults = - device.getHitTestResultsForTransientInput(hitTestSource); - if (hitTestResults.length === 0) { - return []; - } - const results = []; - for (const matrix of hitTestResults) { - results.push(new XRHitTestResult(this, new XRRigidTransform(matrix))); - } - const inputSource = device.getInputSources()[0]; - return [new XRTransientInputHitTestResult(this, results, inputSource)]; - }; - - // - - if (this.nativeWebXR) { - // Note: Even if native WebXR API is available the extension overrides - // it with WebXR polyfill because the extension doesn't work with - // the native one (yet). - overrideAPI(this.global); - this.injected = true; - this._patchNavigatorXR(); - } else { - installEX_API(this.global); - // Note: WebXR API polyfill can be overridden by native WebXR API on the latest Chrome 78 - // after the extension is loaded but before loading page is completed - // if the native WebXR API is disabled via chrome://flags and the page includes - // WebXR origin trial. - // Here is a workaround. Check if XR class is native code when node is appended or - // the page is loaded. If it detects, override WebXR API with the polyfill. - // @TODO: Remove this workaround if the major browser officially support native WebXR API - let overridden = false; - const overrideIfNeeded = () => { - if (overridden) { - return false; - } - if (isNativeFunction(this.global.XRSystem)) { - overrideAPI(this.global); - overridden = true; - return true; - } - return false; - }; - const observer = new MutationObserver((list) => { - for (const record of list) { - for (const node of record.addedNodes) { - if (node.localName === 'script' && overrideIfNeeded()) { - observer.disconnect(); - break; - } - } - if (overridden) { - break; - } - } - }); - observer.observe(document, { subtree: true, childList: true }); - const onLoad = (_event) => { - if (!overridden) { - observer.disconnect(); - overrideIfNeeded(); - } - document.removeEventListener('DOMContentLoaded', onLoad); - }; - document.addEventListener('DOMContentLoaded', onLoad); - } - } - - _patchNavigatorXR() { - const devicePromise = requestXRDevice(this.global); - this.xr = new XRSystem(devicePromise); - Object.defineProperty(this.global.navigator, 'xr', { - value: this.xr, - configurable: true, - }); - } -} - -const requestXRDevice = async (global, _config) => { - // resolve when receiving configuration parameters from content-script as an event - return new Promise((resolve, _reject) => { - const callback = (event) => { - WebXREventDispatcher.instance.removeEventListener(POLYFILL_ACTIONS.DEVICE_INIT, callback); - resolve( - new EmulatedXRDevice( - global, - Object.assign({}, event.detail.deviceDefinition, { - stereoEffect: event.detail.stereoEffect, - }), - ), - ); - }; - WebXREventDispatcher.instance.addEventListener(POLYFILL_ACTIONS.DEVICE_INIT, callback, false); - }); -}; - -// Easy native function detection. -const isNativeFunction = (func) => { - return /\[native code\]/i.test(func.toString()); -}; - -const overrideAPI = (global) => { - console.log( - '[Immersive Web Emulator] native WebXR API successfully overridden', - ); - for (const className in API) { - global[className] = API[className]; - } - installEX_API(global); - - // Since (desktop) Chrome 88 WebGL(2)RenderingContext.makeXRCompatible() seems - // to start to reject if no immersive XR device is plugged in. - // So we need to override them, too. Otherwise JS engines/apps including - // "await context.makeXRCompatible();" won't work with the extension. - // See https://github.com/MozillaReality/WebXR-emulator-extension/issues/266 - if (typeof WebGLRenderingContext !== 'undefined') { - overrideMakeXRCompatible(WebGLRenderingContext); - } - if (typeof WebGL2RenderingContext !== 'undefined') { - overrideMakeXRCompatible(WebGL2RenderingContext); - } -}; - -const installEX_API = (global) => { - for (const className in EX_API) { - global[className] = EX_API[className]; - } -}; - -const overrideMakeXRCompatible = (Context) => { - Context.prototype.makeXRCompatible = function () { - this[XR_COMPATIBLE] = true; - // This is all fake, so just resolve immediately. - return Promise.resolve(); - }; -}; diff --git a/webxr-emulator/EmulatedXRDevice.js b/webxr-emulator/EmulatedXRDevice.js deleted file mode 100644 index f1ed7d3..0000000 --- a/webxr-emulator/EmulatedXRDevice.js +++ /dev/null @@ -1,1155 +0,0 @@ -import { CLIENT_ACTIONS, POLYFILL_ACTIONS } from './actions'; -import { mat4, quat, vec3 } from 'gl-matrix'; - -import GamepadMappings from 'webxr-polyfill/src/devices/GamepadMappings'; -import GamepadXRInputSource from './api/XRGamepadInput'; -import HandXRInputSource from './api/XRHandInput'; -import XRDevice from 'webxr-polyfill/src/devices/XRDevice'; -import { PRIVATE as XRINPUTSOURCE_PRIVATE } from './api/XRInputSource'; -import { PRIVATE as XRSESSION_PRIVATE } from 'webxr-polyfill/src/api/XRSession'; -import XRScene from './XRScene'; -import XRTransientInputHitTestSource from './api/XRTransientInputHitTestSource'; -import { WebXREventDispatcher } from './WebXREventDispatcher'; -import { isClient } from '@ir-engine/common/src/utils/getEnvironment'; - -const DEFAULT_MODES = ['inline']; - -// @TODO: This value should shared with panel.js? -const DEFAULT_HEADSET_POSITION = [0, 1.6, 0]; - -// 10000 is for AR Scene -const APP_CANVAS_Z_INDEX = '9999'; -const AR_CANVAS_Z_INDEX = '9998'; -const DOM_OVERLAY_Z_INDEX = '10001'; - -// For AR -const DEFAULT_RESOLUTION = { width: 1024, height: 2048 }; -const DEFAULT_DEVICE_SIZE = { width: 0.05, height: 0.1, depth: 0.005 }; - -// @TODO: Duplicated with content-scripts.js. Move to somewhere common place? -const dispatchCustomEvent = (type, detail) => { - WebXREventDispatcher.instance.dispatchEvent( - new CustomEvent(type, { - detail: - // eslint-disable-next-line no-undef - typeof cloneInto !== 'undefined' ? cloneInto(detail, window) : detail, - }), - ); -}; - -export default class EmulatedXRDevice extends XRDevice { - // @TODO: write config parameter comment - - constructor(global, config = {}) { - super(global); - - this.sessions = new Map(); - - this.modes = config.modes || DEFAULT_MODES; - this.features = config.features || []; - this.environmentBlendMode = config.environmentBlendMode || 'opaque'; - this.xrScene = new XRScene(); - - // headset - this.position = vec3.copy(vec3.create(), DEFAULT_HEADSET_POSITION); - this.quaternion = quat.create(); - this.scale = vec3.fromValues(1, 1, 1); - this.matrix = mat4.create(); - this.projectionMatrix = mat4.create(); - this.leftProjectionMatrix = mat4.create(); - this.rightProjectionMatrix = mat4.create(); - this.viewMatrix = mat4.create(); - this.leftViewMatrix = mat4.create(); - this.rightViewMatrix = mat4.create(); - - this.handMode = true; - this.handPoseData = { - left: { poseId: 'relaxed', pinchValue: 0, prevPinchValue: 0 }, - right: { poseId: 'relaxed', pinchValue: 0, prevPinchValue: 0 }, - }; - - // controllers - this.gamepads = []; - this.gamepadInputSources = []; - - // hands - this.handGamepads = []; - this.handInputSources = []; - - // other configurations - this.stereoEffectEnabled = - config.stereoEffect !== undefined ? config.stereoEffect : true; - - // @TODO: Edit this comment - // For case where baseLayer's canvas isn't in document.body - - const createCanvasContainer = (zIndex) => { - const canvasContainer = document.createElement('div'); - canvasContainer.style.position = 'fixed'; - canvasContainer.style.width = '100%'; - canvasContainer.style.height = '100%'; - canvasContainer.style.top = '0'; - canvasContainer.style.left = '0'; - canvasContainer.style.zIndex = zIndex; - return canvasContainer; - }; - - if (isClient) { - this.appCanvasContainer = createCanvasContainer(APP_CANVAS_Z_INDEX); - // console.log(this.appCanvasContainer); - this.arCanvasContainer = createCanvasContainer(AR_CANVAS_Z_INDEX); - } - - this.originalCanvasParams = { - parentElement: null, - width: 0, - height: 0, - }; - - // For DOM overlay API - - this.domOverlayRoot = null; - - // For AR - - // Assuming a device supports at most either one VR or AR - this.resolution = - config.resolution !== undefined ? config.resolution : DEFAULT_RESOLUTION; - this.deviceSize = - config.size !== undefined ? config.size : DEFAULT_DEVICE_SIZE; - this.touched = false; - this.isPointerAndTabledCloseEnough = false; // UGH... @TODO: Rename - - this.hitTestSources = []; - this.hitTestResults = new Map(); - - this.hitTestSourcesForTransientInput = []; - this.hitTestResultsForTransientInput = new Map(); - - // - this._initializeControllers(config); - this._initializeHands(); - this._setupEventListeners(); - } - - onBaseLayerSet(sessionId, layer) { - const session = this.sessions.get(sessionId); - - // Remove old canvas first - // if (session.immersive && session.baseLayer) { - // this._removeBaseLayerCanvasFromDiv(sessionId); - // } - - session.baseLayer = layer; - // if (session.immersive && session.baseLayer) { - // this._appendBaseLayerCanvasToDiv(sessionId); - // } - } - - isSessionSupported(mode) { - return this.modes.includes(mode); - } - - isFeatureSupported(featureDescriptor) { - if (this.features.includes(featureDescriptor)) { - return true; - } - switch (featureDescriptor) { - case 'viewer': - return true; - case 'local': - return true; - case 'local-floor': - return true; - case 'bounded-floor': - return false; - case 'unbounded': - return false; - case 'dom-overlay': - return true; - case 'anchors': - return true; - case 'plane-detection': - return true; - case 'hit-test': - return true; - case 'high-fixed-foveation-level': - console.warn( - 'The high-fixed-foveation-level feature is non-standard and deprecated. Refer to the documentation at https://immersive-web.github.io/layers/#dom-xrprojectionlayer-fixedfoveation for the standard way to adjust fixed foveation level.', - ); - return true; - case 'hand-tracking': - return true; - case 'mesh-detection': - return true; - default: - return false; // @TODO: Throw an error? - } - } - - async requestSession(mode, enabledFeatures) { - if (!this.isSessionSupported(mode)) { - return Promise.reject(); - } - const session = new Session(mode, enabledFeatures); - this.sessions.set(session.id, session); - if (mode === 'immersive-ar') { - // document.body.appendChild(this.arCanvasContainer); - // this.xrScene.inject(this.arCanvasContainer); - } - if (session.immersive) { - this.dispatchEvent('@@webxr-polyfill/vr-present-start', session.id); - this._notifyEnterImmersive(); - } - return Promise.resolve(session.id); - } - - requestAnimationFrame(callback) { - return this.global.requestAnimationFrame(callback); - } - - cancelAnimationFrame(handle) { - this.global.cancelAnimationFrame(handle); - } - - onFrameStart(sessionId, renderState) { - const session = this.sessions.get(sessionId); - // guaranteed by the caller that session.baseLayer is not null - const context = session.baseLayer.context; - const canvas = context.canvas; - const near = renderState.depthNear; - const far = renderState.depthFar; - const width = canvas.width; - const height = canvas.height; - - // If session is not an inline session, XRWebGLLayer's composition disabled boolean - // should be false and then framebuffer should be marked as opaque. - // The buffers attached to an opaque framebuffer must be cleared prior to the - // processing of each XR animation frame. - if (session.immersive) { - const currentClearColor = context.getParameter(context.COLOR_CLEAR_VALUE); - const currentClearDepth = context.getParameter(context.DEPTH_CLEAR_VALUE); - const currentClearStencil = context.getParameter( - context.STENCIL_CLEAR_VALUE, - ); - context.clearColor(0.0, 0.0, 0.0, 0.0); - context.clearDepth(1, 0); - context.clearStencil(0.0); - context.clear( - context.DEPTH_BUFFER_BIT | - context.COLOR_BUFFER_BIT | - context.STENCIL_BUFFER_BIT, - ); - context.clearColor( - currentClearColor[0], - currentClearColor[1], - currentClearColor[2], - currentClearColor[3], - ); - context.clearDepth(currentClearDepth); - context.clearStencil(currentClearStencil); - } - - this.gamepads.forEach((gamepad) => { - gamepad.connected = session.immersive; - }); - - if (session.vr || (session.ar && session.immersive)) { - // @TODO: proper FOV - const aspect = (width * (this.stereoEffectEnabled ? 0.5 : 1.0)) / height; - mat4.perspective( - this.leftProjectionMatrix, - Math.PI / 2, - aspect, - near, - far, - ); - mat4.perspective( - this.rightProjectionMatrix, - Math.PI / 2, - aspect, - near, - far, - ); - } else if (session.ar) { - // @TODO: support mobile AR - // @TODO: proper FOV - const aspect = this.deviceSize.width / this.deviceSize.height; - mat4.perspective(this.projectionMatrix, Math.PI / 2, aspect, near, far); - } else { - const aspect = width / height; - mat4.perspective( - this.projectionMatrix, - renderState.inlineVerticalFieldOfView, - aspect, - near, - far, - ); - } - if (session.ar && !session.immersive) { - mat4.fromRotationTranslationScale( - this.matrix, - this.gamepads[1].pose.orientation, - this.gamepads[1].pose.position, - this.scale, - ); - } else { - mat4.fromRotationTranslationScale( - this.matrix, - this.quaternion, - this.position, - this.scale, - ); - } - mat4.invert(this.viewMatrix, this.matrix); - - // Move matrices left/right a bit and then calculate left/rightViewMatrix - // @TODO: proper left/right distance - mat4.invert( - this.leftViewMatrix, - translateOnX(mat4.copy(this.leftViewMatrix, this.matrix), -0.02), - ); - mat4.invert( - this.rightViewMatrix, - translateOnX(mat4.copy(this.rightViewMatrix, this.matrix), 0.02), - ); - - // @TODO: Confirm if input events are only for immersive session - // @TODO: If there are multiple immersive sessions, input events are fired only for the first session. - // Fix this issue (if multiple immersive sessions can be created). - if (session.immersive) { - for (let i = 0; i < this.gamepads.length; ++i) { - const gamepad = this.gamepads[i]; - const inputSourceImpl = this.gamepadInputSources[i]; - const handInputImpl = this.handInputSources[i]; - inputSourceImpl.updateFromGamepad(gamepad); - const handedness = handInputImpl.handedness; - const pinchValue = this.handPoseData[handedness] - ? this.handPoseData[handedness].pinchValue - : 0; - handInputImpl.updateFromGamepad(gamepad, pinchValue); - if (inputSourceImpl.primaryButtonIndex !== -1 && inputSourceImpl.active) { - const primaryActionPressed = - gamepad.buttons[inputSourceImpl.primaryButtonIndex].pressed; - if (primaryActionPressed && !inputSourceImpl.primaryActionPressed) { - this.dispatchEvent('@@webxr-polyfill/input-select-start', { - sessionId: session.id, - inputSource: inputSourceImpl.inputSource, - }); - } else if ( - !primaryActionPressed && - inputSourceImpl.primaryActionPressed - ) { - this.dispatchEvent('@@webxr-polyfill/input-select-end', { - sessionId: session.id, - inputSource: inputSourceImpl.inputSource, - }); - } - // imputSourceImpl.primaryActionPressed is updated in onFrameEnd(). - } - if (inputSourceImpl.primarySqueezeButtonIndex !== -1 && inputSourceImpl.active) { - const primarySqueezeActionPressed = - gamepad.buttons[inputSourceImpl.primarySqueezeButtonIndex].pressed; - if ( - primarySqueezeActionPressed && - !inputSourceImpl.primarySqueezeActionPressed - ) { - this.dispatchEvent('@@webxr-polyfill/input-squeeze-start', { - sessionId: session.id, - inputSource: inputSourceImpl.inputSource, - }); - } else if ( - !primarySqueezeActionPressed && - inputSourceImpl.primarySqueezeActionPressed - ) { - this.dispatchEvent('@@webxr-polyfill/input-squeeze-end', { - sessionId: session.id, - inputSource: inputSourceImpl.inputSource, - }); - } - inputSourceImpl.primarySqueezeActionPressed = - primarySqueezeActionPressed; - } - if ( - this.handPoseData[gamepad.hand].pinchValue == 1 && - this.handPoseData[gamepad.hand].prevPinchValue != 1 - ) { - this.dispatchEvent('@@webxr-polyfill/input-select-start', { - sessionId: session.id, - inputSource: handInputImpl.inputSource, - }); - } - if ( - this.handPoseData[gamepad.hand].pinchValue != 1 && - this.handPoseData[gamepad.hand].prevPinchValue == 1 - ) { - this.dispatchEvent('@@webxr-polyfill/input-select-end', { - sessionId: session.id, - inputSource: handInputImpl.inputSource, - }); - } - this.handPoseData[gamepad.hand].prevPinchValue = - this.handPoseData[gamepad.hand].pinchValue; - } - - this._hitTest(sessionId, this.hitTestSources, this.hitTestResults); - this._hitTest( - sessionId, - this.hitTestSourcesForTransientInput, - this.hitTestResultsForTransientInput, - ); - } - } - - onFrameEnd(sessionId) { - // We handle touch event on AR device as transient input for now. - // If primary action happens on transient input - // 1. First fire intputsourceschange event - // 2. And then fire select start event - // But in webxr-polyfill.js, inputsourceschange event is fired - // after onFrameStart() by making an input source active. - // So I need to postpone input select event until onFrameEnd() here. - // Regarding select and select end events, they should be fired - // before inputsourceschange event, so ok to be in onFrameStart(). - const session = this.sessions.get(sessionId); - if (session.immersive) { - for (let i = 0; i < this.gamepads.length; ++i) { - const gamepad = this.gamepads[i]; - const inputSourceImpl = this.gamepadInputSources[i]; - if (inputSourceImpl.primaryButtonIndex !== -1) { - const primaryActionPressed = - gamepad.buttons[inputSourceImpl.primaryButtonIndex].pressed; - inputSourceImpl.primaryActionPressed = primaryActionPressed; - } - } - } - } - - async requestFrameOfReferenceTransform(type, _options) { - // @TODO: Add note - const matrix = mat4.create(); - switch (type) { - case 'viewer': - case 'local': - matrix[13] = -DEFAULT_HEADSET_POSITION[1]; - return matrix; - - case 'local-floor': - return matrix; - - case 'bounded-floor': - case 'unbound': - default: - // @TODO: Throw an error? - return matrix; - } - } - - endSession(sessionId) { - const session = this.sessions.get(sessionId); - if (session.immersive && session.baseLayer) { - this._removeBaseLayerCanvasFromDiv(sessionId); - this.domOverlayRoot = null; - this.dispatchEvent('@@webxr-polyfill/vr-present-end', sessionId); - this._notifyLeaveImmersive(sessionId); - } - session.ended = true; - } - - doesSessionSupportReferenceSpace(sessionId, type) { - const session = this.sessions.get(sessionId); - if (session.ended) { - return false; - } - return session.enabledFeatures.has(type); - } - - getViewport(sessionId, eye, _layer, target) { - const session = this.sessions.get(sessionId); - const canvas = session.baseLayer.context.canvas; - const width = canvas.width; - const height = canvas.height; - if (session.ar && !session.immersive) { - // Currently the polyfill let any immersive mode has two ViewSpaces left and right. - // Return the same viewport for any eye type so far. - // @TODO: Send feedback to webxr-polyfill.js about one 'none' view option - // for AR monoscopic device - target.x = 0; - target.y = 0; - target.width = width; - target.height = height; - } else { - if (eye === 'none') { - target.x = 0; - target.width = width; - } else if (this.stereoEffectEnabled) { - target.x = eye === 'left' ? 0 : width / 2; - target.width = width / 2; - } else { - target.x = 0; - target.width = eye === 'left' ? width : 0; - } - target.y = 0; - target.height = height; - } - return true; - } - - getProjectionMatrix(eye) { - return eye === 'none' - ? this.projectionMatrix - : eye === 'left' - ? this.leftProjectionMatrix - : this.rightProjectionMatrix; - } - - getBasePoseMatrix() { - return this.matrix; - } - - getBaseViewMatrix(eye) { - if (eye === 'none' || !this.stereoEffectEnabled) { - return this.viewMatrix; - } - return eye === 'left' ? this.leftViewMatrix : this.rightViewMatrix; - } - - getInputSources() { - const inputSources = []; - for (const inputSourceImpl of this.handMode - ? this.handInputSources - : this.gamepadInputSources) { - if (inputSourceImpl.active) { - inputSources.push(inputSourceImpl.inputSource); - } - } - - return inputSources; - } - - getInputPose(inputSource, coordinateSystem, poseType) { - for (const inputSourceImpl of [].concat( - this.gamepadInputSources, - this.handInputSources, - )) { - if (inputSourceImpl.inputSource === inputSource) { - const pose = inputSourceImpl.getXRPose(coordinateSystem, poseType); - - return pose; - } - } - return null; - } - - onWindowResize() { - // @TODO: implement - } - - // DOM Overlay API - - setDomOverlayRoot(root) { - this.domOverlayRoot = root; - } - - // AR Hitting test - - addHitTestSource(source) { - this.hitTestSources.push(source); - } - - getHitTestResults(source) { - return this.hitTestResults.get(source) || []; - } - - addHitTestSourceForTransientInput(source) { - this.hitTestSourcesForTransientInput.push(source); - } - - getHitTestResultsForTransientInput(source) { - return this.hitTestResultsForTransientInput.get(source) || []; - } - - _hitTest(sessionId, hitTestSources, hitTestResults) { - // Remove inactive sources first - let activeHitTestSourceNum = 0; - for (let i = 0; i < hitTestSources.length; i++) { - const source = hitTestSources[i]; - if (source._active) { - hitTestSources[activeHitTestSourceNum++] = source; - } - } - hitTestSources.length = activeHitTestSourceNum; - - // Do hit test next - hitTestResults.clear(); - for (const source of hitTestSources) { - if (sessionId !== source._session[XRSESSION_PRIVATE].id) { - continue; - } - - // Gets base matrix depending on hit test source type - let baseMatrix; - if (source instanceof XRTransientInputHitTestSource) { - if (!this.gamepadInputSources[0].active) { - continue; - } - if (!source._profile.includes('touch')) { - continue; - } - const gamepad = this.gamepads[0]; - const matrix = mat4.identity(mat4.create()); - matrix[12] = gamepad.axes[0]; - matrix[13] = -gamepad.axes[1]; - baseMatrix = mat4.multiply(matrix, this.matrix, matrix); - } else { - baseMatrix = source._space._baseMatrix; - if (!baseMatrix) { - continue; - } - } - - // Calculates origin and direction used for hit test in AR scene - const offsetRay = source._offsetRay; - const origin = vec3.set( - vec3.create(), - offsetRay.origin.x, - offsetRay.origin.y, - offsetRay.origin.z, - ); - const direction = vec3.set( - vec3.create(), - offsetRay.direction.x, - offsetRay.direction.y, - offsetRay.direction.z, - ); - vec3.transformMat4(origin, origin, baseMatrix); - vec3.transformQuat( - direction, - direction, - mat4.getRotation(quat.create(), baseMatrix), - ); - - // Do hit test in AR scene and stores the result matrices - const arHitTestResults = this.xrScene.getHitTestResults( - origin, - direction, - ); - hitTestResults.set(source, arHitTestResults); - } - } - - // Private methods - - // If session is immersive mode, resize the canvas size to full window size. - // To do that, changing canvas size and moving the canvas to - // the special div. They are restored when exiting immersive mode. - // @TODO: Simplify the method names - - _appendBaseLayerCanvasToDiv(sessionId) { - const session = this.sessions.get(sessionId); - const canvas = session.baseLayer.context.canvas; - - this.originalCanvasParams.width = canvas.width; - this.originalCanvasParams.height = canvas.height; - - document.body.appendChild(this.appCanvasContainer); - - // If canvas is OffscreenCanvas we don't further touch so far. - if (!(canvas instanceof HTMLCanvasElement)) { - return; - } - - this.originalCanvasParams.parentElement = canvas.parentElement; - canvas.width = window.innerWidth; - canvas.height = window.innerHeight; - this.appCanvasContainer.appendChild(canvas); - - // DOM overlay API - // @TODO: Is this the best place to handle? - // @TODO: What if dom element is appened/removed while in immersive mode? - // Should we observe? - if (this.domOverlayRoot) { - const el = this.domOverlayRoot; - el.style._zIndex = el.style.zIndex; // Polluting is bad... - el.style.zIndex = DOM_OVERLAY_Z_INDEX; - } - } - - _removeBaseLayerCanvasFromDiv(sessionId) { - const session = this.sessions.get(sessionId); - const canvas = session.baseLayer.context.canvas; - - canvas.width = this.originalCanvasParams.width; - canvas.height = this.originalCanvasParams.height; - - // There may be a case where an application operates DOM elements - // in immersive mode. In such case, we don't restore DOM elements - // hierarchies so far. - if (this.appCanvasContainer.parentElement === document.body) { - document.body.removeChild(this.appCanvasContainer); - } - if (canvas.parentElement === this.appCanvasContainer) { - this.appCanvasContainer.removeChild(canvas); - } - - // If canvas is OffscreenCanvas we don't touch so far. - if (!(canvas instanceof HTMLCanvasElement)) { - return; - } - - if (this.originalCanvasParams.parentElement) { - this.originalCanvasParams.parentElement.appendChild(canvas); - } - this.originalCanvasParams.parentElement = null; - - // DOM overlay API - // @TODO: Is this the best place to handle? - if (this.domOverlayRoot) { - const el = this.domOverlayRoot; - el.style.zIndex = el.style._zIndex; - delete el.style._zIndex; - this.appCanvasContainer.style.zIndex = APP_CANVAS_Z_INDEX; - } - } - - // For AR. Check if right controller(pointer) is touched with left controller(tablet) - - // UGH... @TODO: Rename - _isPointerCloseEnoughToTablet() { - // @TODO: Optimize if possible - const pose = this.gamepads[0].pose; - const matrix = mat4.fromRotationTranslation( - mat4.create(), - pose.orientation, - pose.position, - ); - mat4.multiply(matrix, this.viewMatrix, matrix); - const dx = matrix[12] / (this.deviceSize.width * 0.5); - const dy = matrix[13] / (this.deviceSize.height * 0.5); - const dz = matrix[14]; - return ( - dx <= 1.0 && - dx >= -1.0 && - dy <= 1.0 && - dy >= -1.0 && - dz <= 0.01 && - dz >= 0.0 - ); - } - - _getTouchCoordinates() { - // @TODO: Optimize if possible - const pose = this.gamepads[0].pose; - const matrix = mat4.fromRotationTranslation( - mat4.create(), - pose.orientation, - pose.position, - ); - mat4.multiply(matrix, this.viewMatrix, matrix); - const dx = matrix[12] / (this.deviceSize.width * 0.5); - const dy = matrix[13] / (this.deviceSize.height * 0.5); - return [dx, dy]; - } - - // Notify the update to panel - - _notifyPoseUpdate() { - dispatchCustomEvent('device-pose', { - position: this.position, - quaternion: this.quaternion, - }); - } - - // controllerIndex: 0 => Right, 1 => Left - _notifyInputPoseUpdate(controllerIndex) { - const pose = this.gamepads[controllerIndex].pose; - const objectName = - controllerIndex === 0 ? 'right-controller' : 'left-controller'; - dispatchCustomEvent('device-input-pose', { - position: pose.position, - quaternion: pose.orientation, - objectName: objectName, - }); - } - - _notifyEnterImmersive() { - dispatchCustomEvent(CLIENT_ACTIONS.ENTER_IMMERSIVE, {}); - } - - _notifyLeaveImmersive(sessionId) { - // const session = this.sessions.get(sessionId); - // if (session.mode === 'immersive-ar') { - // this.arCanvasContainer.remove(); - // } - dispatchCustomEvent(CLIENT_ACTIONS.EXIT_IMMERSIVE, {}); - } - - // Device status update methods invoked from event listeners. - - _updateStereoEffect(enabled) { - this.stereoEffectEnabled = enabled; - } - - _updatePose(positionArray, quaternionArray) { - for (let i = 0; i < 3; i++) { - this.position[i] = positionArray[i]; - } - for (let i = 0; i < 4; i++) { - this.quaternion[i] = quaternionArray[i]; - } - } - - _updateInputPose(positionArray, quaternionArray, index) { - if (index >= this.gamepads.length) { - return; - } - const gamepad = this.gamepads[index]; - const pose = gamepad.pose; - for (let i = 0; i < 3; i++) { - pose.position[i] = positionArray[i]; - } - for (let i = 0; i < 4; i++) { - pose.orientation[i] = quaternionArray[i]; - } - this.gamepadInputSources[index].inputSource[ - XRINPUTSOURCE_PRIVATE - ].targetRaySpace._baseMatrix = - this.gamepadInputSources[index].basePoseMatrix; - } - - _updateInputButtonPressed(pressed, controllerIndex, buttonIndex) { - if (controllerIndex >= this.gamepads.length) { - return; - } - const gamepad = this.gamepads[controllerIndex]; - if (buttonIndex >= gamepad.buttons.length) { - return; - } - gamepad.buttons[buttonIndex].pressed = pressed; - gamepad.buttons[buttonIndex].value = pressed ? 1.0 : 0.0; - } - - _updateInputButton( - controllerIndex, - buttonIndex, - pressed, - touched = false, - value = null, - ) { - if (controllerIndex >= this.gamepads.length) { - return; - } - const gamepad = this.gamepads[controllerIndex]; - if (buttonIndex >= gamepad.buttons.length) { - return; - } - gamepad.buttons[buttonIndex].pressed = pressed; - gamepad.buttons[buttonIndex].touched = touched; - if (value != null) { - gamepad.buttons[buttonIndex].value = value; - } else { - gamepad.buttons[buttonIndex].value = pressed ? 1.0 : 0.0; - } - } - - _updateInputAxisValue(value, controllerIndex, axisIndex) { - if (controllerIndex >= this.gamepads.length) { - return; - } - const gamepad = this.gamepads[controllerIndex]; - if (axisIndex >= gamepad.axes.length) { - return; - } - gamepad.axes[axisIndex] = value; - } - - _updateControllerInputVisibility(controllerIndex, visible) { - if (controllerIndex >= this.gamepadInputSources.length) { - throw new Error('ControllerIndex ' + controllerIndex + ' is greater than the gamepadInputSources.length ' + this.gamepadInputSources.length); - } - const inputSourceImpl = this.gamepadInputSources[controllerIndex]; - inputSourceImpl.active = visible; - } - - _updateHandInputVisibility(handIndex, visible) { - if (handIndex >= this.handInputSources.length) { - throw new Error('HandIndex ' + handIndex + ' is greater than the handInputSources.length ' + this.handInputSources.length); - } - const inputSourceImpl = this.handInputSources[handIndex]; - inputSourceImpl.active = visible; - } - - _initializeControllers(config) { - const hasController = config.controllers !== undefined; - const controllerNum = hasController ? config.controllers.length : 0; - this.gamepads.length = 0; - this.gamepadInputSources.length = 0; - for (let i = 0; i < controllerNum; i++) { - const controller = config.controllers[i]; - const id = controller.id || ''; - const hasPosition = controller.hasPosition || false; - const buttonNum = controller.buttonNum || 0; - const primaryButtonIndex = - controller.primaryButtonIndex !== undefined - ? controller.primaryButtonIndex - : 0; - const primarySqueezeButtonIndex = - controller.primarySqueezeButtonIndex !== undefined - ? controller.primarySqueezeButtonIndex - : -1; - this.gamepads.push( - createGamepad(id, i === 0 ? 'right' : 'left', buttonNum, hasPosition), - ); - // @TODO: targetRayMode should be screen for right controller(pointer) in AR - const imputSourceImpl = new GamepadXRInputSource( - this, - {}, - primaryButtonIndex, - primarySqueezeButtonIndex, - ); - imputSourceImpl.active = true; // Override property for transient imput - imputSourceImpl.profilesOverride = - GamepadMappings[controller.id].profiles; - this.gamepadInputSources.push(imputSourceImpl); - } - } - - _initializeHands() { - this.handInputSources.length = 0; - for (let i = 0; i < 2; i++) { - const handInputImpl = new HandXRInputSource(this); - handInputImpl.active = true; - this.handInputSources.push(handInputImpl); - } - } - - // Set up event listeners. Events are sent from panel via background. - - _setupEventListeners() { - WebXREventDispatcher.instance.addEventListener(POLYFILL_ACTIONS.DEVICE_TYPE_CHANGE, (event) => { - const config = event.detail.deviceDefinition; - - this.modes = config.modes || DEFAULT_MODES; - this.features = config.features || []; - this.resolution = - config.resolution !== undefined - ? config.resolution - : DEFAULT_RESOLUTION; - this.deviceSize = - config.size !== undefined ? config.size : DEFAULT_DEVICE_SIZE; - - // Note: Just in case release primary buttons and wait for two frames to fire selectend event - // before initialize controllers. - // @TODO: Very hacky. We should go with more proper way. - for (let i = 0; i < this.gamepads.length; ++i) { - const gamepad = this.gamepads[i]; - const inputSourceImpl = this.gamepadInputSources[i]; - inputSourceImpl.active = true; - if (inputSourceImpl.primaryButtonIndex !== -1) { - gamepad.buttons[inputSourceImpl.primaryButtonIndex].pressed = false; - } - if (inputSourceImpl.primarySqueezeButtonIndex !== -1) { - gamepad.buttons[ - inputSourceImpl.primarySqueezeButtonIndex - ].pressed = false; - } - } - - this.requestAnimationFrame(() => { - this.requestAnimationFrame(() => { - this._initializeControllers(config); - }); - }); - }); - - WebXREventDispatcher.instance.addEventListener( - POLYFILL_ACTIONS.HEADSET_POSE_CHANGE, - (event) => { - const positionArray = event.detail.position; - const quaternionArray = event.detail.quaternion; - this._updatePose(positionArray, quaternionArray); - if (this.xrScene) { - this.xrScene.updateCameraTransform(positionArray, quaternionArray); - } - }, - false, - ); - - WebXREventDispatcher.instance.addEventListener( - POLYFILL_ACTIONS.CONTROLLER_POSE_CHANGE, - (event) => { - const positionArray = event.detail.position; - const quaternionArray = event.detail.quaternion; - const objectName = event.detail.objectName; - - switch (objectName) { - case 'right-controller': - case 'left-controller': - this._updateInputPose( - positionArray, - quaternionArray, - objectName === 'right-controller' ? 0 : 1, - ); // @TODO: remove magic number - break; - } - }, - ); - - WebXREventDispatcher.instance.addEventListener( - POLYFILL_ACTIONS.CONTROLLER_VISIBILITY_CHANGE, - (event) => { - const objectName = event.detail.objectName; - const visible = event.detail.visible; - - switch (objectName) { - case 'right-controller': - case 'left-controller': - this._updateControllerInputVisibility( - objectName === 'right-controller' ? 0 : 1, // @TODO: remove magic number - visible, - ); - break; - } - }, - ); - - WebXREventDispatcher.instance.addEventListener( - POLYFILL_ACTIONS.BUTTON_STATE_CHANGE, - (event) => { - const objectName = event.detail.objectName; - const buttonIndex = event.detail.buttonIndex; - const pressed = event.detail.pressed; - const touched = event.detail.touched; - const value = event.detail.value; - - switch (objectName) { - case 'right-controller': - case 'left-controller': - this._updateInputButton( - objectName === 'right-controller' ? 0 : 1, // @TODO: remove magic number - buttonIndex, - pressed, - touched, - value, - ); - break; - } - }, - false, - ); - - WebXREventDispatcher.instance.addEventListener( - POLYFILL_ACTIONS.ANALOG_VALUE_CHANGE, - (event) => { - const value = event.detail.value; - const objectName = event.detail.objectName; - const axisIndex = event.detail.axisIndex; - - switch (objectName) { - case 'right-controller': - case 'left-controller': - this._updateInputAxisValue( - value, - objectName === 'right-controller' ? 0 : 1, // @TODO: remove magic number - axisIndex, - ); - break; - } - }, - false, - ); - - WebXREventDispatcher.instance.addEventListener(POLYFILL_ACTIONS.ROOM_DIMENSION_CHANGE, (event) => { - const dimension = event.detail.dimension; - this.xrScene.createRoom(dimension); - }); - - WebXREventDispatcher.instance.addEventListener(POLYFILL_ACTIONS.STEREO_TOGGLE, (event) => { - this._updateStereoEffect(event.detail.enabled); - }); - - WebXREventDispatcher.instance.addEventListener(POLYFILL_ACTIONS.INPUT_MODE_CHANGE, (event) => { - this.handMode = event.detail.inputMode === 'hands'; - }); - - WebXREventDispatcher.instance.addEventListener(POLYFILL_ACTIONS.HAND_POSE_CHANGE, (event) => { - const handedness = event.detail.handedness; - const poseId = event.detail.pose; - this.handPoseData[handedness].poseId = poseId; - }); - - WebXREventDispatcher.instance.addEventListener( - POLYFILL_ACTIONS.HAND_VISIBILITY_CHANGE, - (event) => { - const handedness = event.detail.handedness; - const visible = event.detail.visible; - - switch (handedness) { - case 'right': - case 'left': - this._updateHandInputVisibility( - handedness === 'right' ? 0 : 1, // @TODO: remove magic number - visible, - ); - break; - } - }, - ); - - WebXREventDispatcher.instance.addEventListener(POLYFILL_ACTIONS.PINCH_VALUE_CHANGE, (event) => { - const handedness = event.detail.handedness; - const pinchValue = event.detail.value; - this.handPoseData[handedness].pinchValue = pinchValue; - }); - - WebXREventDispatcher.instance.addEventListener(POLYFILL_ACTIONS.USER_OBJECTS_CHANGE, (event) => { - const objects = event.detail.objects; - this.xrScene.updateUserObjects(objects); - }); - } -} - -let SESSION_ID = 0; -class Session { - constructor(mode, enabledFeatures) { - this.mode = mode; - // @TODO support mobile non-immersive - this.immersive = mode == 'immersive-vr' || mode == 'immersive-ar'; - this.vr = mode === 'immersive-vr'; - this.ar = mode == 'immersive-ar'; - this.id = ++SESSION_ID; - this.baseLayer = null; - this.inlineVerticalFieldOfView = Math.PI * 0.5; - this.ended = false; - this.enabledFeatures = enabledFeatures; - } -} - -const createGamepad = (id, hand, buttonNum, hasPosition) => { - const buttons = []; - for (let i = 0; i < buttonNum; i++) { - buttons.push({ - pressed: false, - touched: false, - value: 0.0, - }); - } - return { - id: id || '', - pose: { - hasPosition: hasPosition, - position: [0, 0, 0], - orientation: [0, 0, 0, 1], - }, - buttons: buttons, - hand: hand, - mapping: 'xr-standard', - axes: [0, 0], - }; -}; - -const tmpVec3 = vec3.create(); -const translateOnX = (matrix, distance) => { - vec3.set(tmpVec3, distance, 0, 0); - return mat4.translate(matrix, matrix, tmpVec3); -}; diff --git a/webxr-emulator/LICENSE.md b/webxr-emulator/LICENSE.md deleted file mode 100644 index b93be90..0000000 --- a/webxr-emulator/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) Meta Platforms, Inc. and affiliates. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/webxr-emulator/OldCustomWebXRPolyfill.ts b/webxr-emulator/OldCustomWebXRPolyfill.ts deleted file mode 100644 index 975bd92..0000000 --- a/webxr-emulator/OldCustomWebXRPolyfill.ts +++ /dev/null @@ -1,220 +0,0 @@ -// @ts-nocheck -import EX_API from './api/index' -import XRHitTestResult from './api/XRHitTestResult' -import XRHitTestSource from './api/XRHitTestSource' -import XRTransientInputHitTestResult from './api/XRTransientInputHitTestResult' -import XRTransientInputHitTestSource from './api/XRTransientInputHitTestSource' -import EmulatedXRDevice from './EmulatedXRDevice' -import API from './webxr-polyfill/api/index' -import XRFrame from './webxr-polyfill/api/XRFrame' -import XRRigidTransform from './webxr-polyfill/api/XRRigidTransform' -import XRSession, { PRIVATE as XRSESSION_PRIVATE } from './webxr-polyfill/api/XRSession' -import XRSystem from './webxr-polyfill/api/XRSystem' -import { XR_COMPATIBLE } from './webxr-polyfill/constants' -import WebXRPolyfill from './webxr-polyfill/WebXRPolyfill' -import { WebXREventDispatcher } from './WebXREventDispatcher' - -/** - * Adapted from the mozilla webxr emulator - * https://github.com/MozillaReality/WebXR-emulator-extension - */ - -export class IREngineWebXRPolyfill extends WebXRPolyfill { - constructor() { - super({ global: globalThis }) - // Note: Experimental. - // Override some XR APIs to track active immersive session to - // enable to exit immersive by the extension. - // Exiting without user gesture in the page might violate security policy - // so there might be a chance that we remove this feature at some point. - - let activeImmersiveSession = null - const originalRequestSession = XRSystem.prototype.requestSession - XRSystem.prototype.requestSession = function (mode, enabledFeatures: any = {}) { - return originalRequestSession.call(this, mode, enabledFeatures).then((session) => { - if (mode === 'immersive-vr' || mode === 'immersive-ar') { - activeImmersiveSession = session - - // DOM-Overlay API - const optionalFeatures = enabledFeatures.optionalFeatures - const domOverlay = enabledFeatures.domOverlay - if (optionalFeatures && optionalFeatures.includes('dom-overlay') && domOverlay && domOverlay.root) { - const device = session[XRSESSION_PRIVATE].device - device.setDomOverlayRoot(domOverlay.root) - session.domOverlayState = { type: 'screen' } - } - } - return session - }) - } - - const originalEnd = XRSession.prototype.end - XRSession.prototype.end = function () { - return originalEnd.call(this).then(() => { - if (activeImmersiveSession === this) { - activeImmersiveSession = null - } - }) - } - - WebXREventDispatcher.instance.addEventListener('webxr-exit-immersive', (event) => { - if (activeImmersiveSession && !activeImmersiveSession.ended) { - activeImmersiveSession.end().then(() => { - activeImmersiveSession = null - }) - } - }) - - // Extending XRSession and XRFrame for AR hitting test API. - ;(XRSession.prototype as any).requestHitTestSource = function (options) { - const source = new XRHitTestSource(this, options) - const device = this[XRSESSION_PRIVATE].device - device.addHitTestSource(source) - return Promise.resolve(source) - } - ;(XRSession.prototype as any).requestHitTestSourceForTransientInput = function (options) { - const source = new XRTransientInputHitTestSource(this, options) - const device = this[XRSESSION_PRIVATE].device - device.addHitTestSourceForTransientInput(source) - return Promise.resolve(source) - } - ;(XRFrame.prototype as any).getHitTestResults = function (hitTestSource) { - const device = this.session[XRSESSION_PRIVATE].device - const hitTestResults = device.getHitTestResults(hitTestSource) - const results = [] - for (const matrix of hitTestResults) { - results.push(new XRHitTestResult(this, new XRRigidTransform(matrix))) - } - return results - } - ;(XRFrame.prototype as any).getHitTestResultsForTransientInput = function (hitTestSource) { - const device = this.session[XRSESSION_PRIVATE].device - const hitTestResults = device.getHitTestResultsForTransientInput(hitTestSource) - if (hitTestResults.length === 0) { - return [] - } - const results = [] - for (const matrix of hitTestResults) { - results.push(new XRHitTestResult(this, new XRRigidTransform(matrix))) - } - const inputSource = device.getInputSources()[0] - return [new XRTransientInputHitTestResult(this, results, inputSource)] - } - - if (this.nativeWebXR) { - // Note: Even if native WebXR API is available the extension overrides - // it with WebXR polyfill because the extension doesn't work with - // the native one (yet). - overrideAPI() - this.injected = true - this._patchNavigatorXR() - } else { - installEX_API() - // Note: WebXR API polyfill can be overridden by native WebXR API on the latest Chrome 78 - // after the extension is loaded but before loading page is completed - // if the native WebXR API is disabled via chrome://flags and the page includes - // WebXR origin trial. - // Here is a workaround. Check if XR class is native code when node is appended or - // the page is loaded. If it detects, override WebXR API with the polyfill. - // @TODO: Remove this workaround if the major browser officially support native WebXR API - let overridden = false - const overrideIfNeeded = () => { - if (overridden) { - return false - } - if (isNativeFunction(this.global.XRSystem)) { - overrideAPI() - overridden = true - return true - } - return false - } - const observer = new MutationObserver((list) => { - for (const record of list) { - for (const node of record.addedNodes) { - if ((node as any).localName === 'script' && overrideIfNeeded()) { - observer.disconnect() - break - } - } - if (overridden) { - break - } - } - }) - observer.observe(document, { subtree: true, childList: true }) - const onLoad = (event) => { - if (!overridden) { - observer.disconnect() - overrideIfNeeded() - } - document.removeEventListener('DOMContentLoaded', onLoad) - } - document.addEventListener('DOMContentLoaded', onLoad) - } - } - - _patchNavigatorXR() { - const devicePromise = requestXRDevice() - this.xr = new XRSystem(devicePromise) - Object.defineProperty(this.global.navigator, 'xr', { - value: this.xr, - configurable: true - }) - } -} - -const requestXRDevice = async () => { - // resolve when receiving configuration parameters from content-script as an event - return new Promise((resolve, reject) => { - const callback = (event) => { - WebXREventDispatcher.instance.removeEventListener('webxr-device-init', callback) - resolve( - new EmulatedXRDevice( - globalThis, - Object.assign({}, event.detail.deviceDefinition, { stereoEffect: event.detail.stereoEffect }) - ) - ) - } - WebXREventDispatcher.instance.addEventListener('webxr-device-init', callback, false) - }) -} - -// Easy native function detection. -const isNativeFunction = (func) => { - return /\[native code\]/i.test(func.toString()) -} - -const overrideAPI = () => { - console.log('WebXR emulator extension overrides native WebXR API with polyfill.') - for (const className in API) { - globalThis[className] = API[className] - } - installEX_API() - - // Since (desktop) Chrome 88 WebGL(2)RenderingContext.makeXRCompatible() seems - // to start to reject if no immersive XR device is plugged in. - // So we need to override them, too. Otherwise JS engines/apps including - // "await context.makeXRCompatible();" won't work with the extension. - // See https://github.com/MozillaReality/WebXR-emulator-extension/issues/266 - if (typeof WebGLRenderingContext !== 'undefined') { - overrideMakeXRCompatible(WebGLRenderingContext) - } - if (typeof WebGL2RenderingContext !== 'undefined') { - overrideMakeXRCompatible(WebGL2RenderingContext) - } -} - -const installEX_API = () => { - for (const className in EX_API) { - globalThis[className] = EX_API[className] - } -} - -const overrideMakeXRCompatible = (Context) => { - Context.prototype.makeXRCompatible = function () { - this[XR_COMPATIBLE] = true - // This is all fake, so just resolve immediately. - return Promise.resolve() - } -} diff --git a/webxr-emulator/WebXREventDispatcher.ts b/webxr-emulator/WebXREventDispatcher.ts deleted file mode 100644 index b11a2bc..0000000 --- a/webxr-emulator/WebXREventDispatcher.ts +++ /dev/null @@ -1,57 +0,0 @@ -export class WebXREventDispatcher { - public static instance: WebXREventDispatcher = new WebXREventDispatcher() - _listeners = {} - public reset(): void { - Object.keys(WebXREventDispatcher.instance._listeners).forEach((key) => { - delete WebXREventDispatcher.instance._listeners[key] - }) - } - once(eventName: string | number, listener: Function, ...args: any): void { - const onEvent = (ev) => { - WebXREventDispatcher.instance.removeEventListener(eventName, onEvent) - listener(ev) - } - WebXREventDispatcher.instance.addEventListener(eventName, onEvent) - } - addEventListener(eventName: string | number, listener: Function, ...args: any): void { - const listeners = WebXREventDispatcher.instance._listeners - if (listeners[eventName] === undefined) { - listeners[eventName] = [] - } - - if (listeners[eventName].indexOf(listener) === -1) { - listeners[eventName].push(listener) - } - } - hasEventListener(eventName: string | number, listener: Function, ...args: any): boolean { - return ( - WebXREventDispatcher.instance._listeners[eventName] !== undefined && - WebXREventDispatcher.instance._listeners[eventName].indexOf(listener) !== -1 - ) - } - removeEventListener(eventName: string | number, listener: Function, ...args: any): void { - const listenerArray = WebXREventDispatcher.instance._listeners[eventName] - if (listenerArray !== undefined) { - const index = listenerArray.indexOf(listener) - if (index !== -1) { - listenerArray.splice(index, 1) - } - } - } - removeAllListenersForEvent(eventName: string, deleteEvent?: boolean, ...args: any) { - if (deleteEvent) { - delete WebXREventDispatcher.instance._listeners[eventName] - } else { - WebXREventDispatcher.instance._listeners[eventName] = [] - } - } - dispatchEvent(event: { type: string; [attachment: string]: any }, ...args: any): void { - const listenerArray = WebXREventDispatcher.instance._listeners[event.type] - if (listenerArray !== undefined) { - const array = listenerArray.slice(0) - for (let i = 0; i < array.length; i++) { - array[i].call(WebXREventDispatcher.instance, event, ...args) - } - } - } -} diff --git a/webxr-emulator/XRScene.js b/webxr-emulator/XRScene.js deleted file mode 100644 index fcb4dd4..0000000 --- a/webxr-emulator/XRScene.js +++ /dev/null @@ -1,382 +0,0 @@ -import { - BoxGeometry, - Color, - DirectionalLight, - DoubleSide, - LineBasicMaterial, - LineSegments, - Mesh, - MeshBasicMaterial, - Object3D, - PerspectiveCamera, - PlaneGeometry, - Raycaster, - Scene, - Vector3, - WebGLRenderer, -} from 'three'; -import { XRPlane, XRPlaneOrientation } from './api/XRPlane'; - -import { BoxLineGeometry } from 'three/examples/jsm/geometries/BoxLineGeometry.js'; -import { XRMesh } from './api/XRMesh'; -import XRSpace from 'webxr-polyfill/src/api/XRSpace'; -import { mat4 } from 'gl-matrix'; -import { isClient } from '@ir-engine/common/src/utils/getEnvironment'; - -const DEFAULT_CAMERA_POSITION = [0, 1.6, 0]; -const PLANE_CONFIG = { - FLOOR: { - orientation: XRPlaneOrientation.Horizontal, - quaternion: [0, 0, 0, 1], - semanticLabel: 'floor', - }, - CEILING: { - orientation: XRPlaneOrientation.Horizontal, - quaternion: [0, 0, 1, 0], - semanticLabel: 'ceiling', - }, - RIGHT: { - orientation: XRPlaneOrientation.Vertical, - quaternion: [0, 0, 0.7071068, 0.7071068], - semanticLabel: 'wall', - }, - LEFT: { - orientation: XRPlaneOrientation.Vertical, - quaternion: [0, 0, -0.7071068, 0.7071068], - semanticLabel: 'wall', - }, - FRONT: { - orientation: XRPlaneOrientation.Vertical, - quaternion: [0.7071068, 0, 0, 0.7071068], - semanticLabel: 'wall', - }, - BACK: { - orientation: XRPlaneOrientation.Vertical, - quaternion: [-0.7071068, 0, 0, 0.7071068], - semanticLabel: 'wall', - }, -}; -const DEFAULT_ROOM_DIMENSION = { - x: 6, - y: 3, - z: 6, -}; - -const buildXRPlane = (width, length, position, planeConfig) => { - const planeMatrix = new Float32Array(16); - mat4.fromRotationTranslation(planeMatrix, planeConfig.quaternion, position); - const planeSpace = new XRSpace(); - planeSpace._baseMatrix = planeMatrix; - const points = [ - new DOMPointReadOnly(width, 0, length), - new DOMPointReadOnly(width, 0, -length), - new DOMPointReadOnly(-width, 0, -length), - new DOMPointReadOnly(-width, 0, length), - new DOMPointReadOnly(width, 0, length), - ]; - return new XRPlane( - planeSpace, - points, - planeConfig.orientation, - planeConfig.semanticLabel, - ); -}; - -/** - * @param {THREE.Mesh} mesh - */ -const buildXRMesh = (mesh) => { - const meshMatrix = new Float32Array(16); - mat4.fromRotationTranslation( - meshMatrix, - mesh.quaternion.toArray(), - mesh.position.toArray(), - ); - const meshSpace = new XRSpace(); - meshSpace._baseMatrix = meshMatrix; - const indices = mesh.geometry.index.array; - const vertices = mesh.geometry.getAttribute('position').array; - const semanticLabel = mesh.userData.semanticLabel; - return new XRMesh(meshSpace, vertices, indices, semanticLabel); -}; - -class XRRoomFactory { - constructor(scene) { - this.scene = scene; - this.roomObject = null; - this.roomCollider = null; - this.xrPlanes = []; - } - - createRoom(x, y, z) { - if (this.roomObject) this.scene.remove(this.roomObject); - if (this.roomCollider) this.scene.remove(this.roomCollider); - this.roomObject = new LineSegments( - new BoxLineGeometry( - x, - y, - z, - Math.ceil(x * 2), - Math.ceil(y * 2), - Math.ceil(z * 2), - ), - new LineBasicMaterial({ color: 0x808080 }), - ); - this.roomObject.geometry.translate(0, y / 2, 0); - this.scene.add(this.roomObject); - this.xrPlanes = [ - buildXRPlane(x / 2, z / 2, [0, 0, 0], PLANE_CONFIG.FLOOR), - buildXRPlane(x / 2, z / 2, [0, y, 0], PLANE_CONFIG.CEILING), - buildXRPlane(y / 2, z / 2, [x / 2, y / 2, 0], PLANE_CONFIG.RIGHT), - buildXRPlane(y / 2, z / 2, [-x / 2, y / 2, 0], PLANE_CONFIG.LEFT), - buildXRPlane(x / 2, y / 2, [0, y / 2, z / 2], PLANE_CONFIG.BACK), - buildXRPlane(x / 2, y / 2, [0, y / 2, -z / 2], PLANE_CONFIG.FRONT), - ]; - this.roomCollider = new Mesh( - new BoxGeometry(x, y, z), - new MeshBasicMaterial({ - side: DoubleSide, - }), - ); - this.roomCollider.visible = false; - this.roomCollider.position.y = y / 2; - this.scene.add(this.roomCollider); - } -} - -export default class XRScene { - constructor() { - this.renderer = null; - this.camera = null; - - this.onCameraPoseUpdate = null; - this.hitTestTarget = null; - - this._init(); - } - - _init() { - const width = window.innerWidth; - const height = window.innerHeight; - - if (isClient) { - const canvas = document.createElement('canvas'); - const context = canvas.getContext('webgl2', { antialias: true }); - context.globalCompositeOperation = 'destination-over'; - - const renderer = new WebGLRenderer({ canvas: canvas, context: context }); - renderer.setSize(width, height); - canvas.width = width; - canvas.height = height; - renderer.domElement.oncontextmenu = () => { - return false; - }; - this.renderer = renderer; - } - - const scene = new Scene(); - scene.background = new Color(0x444444); - - this.roomFactory = new XRRoomFactory(scene); - this.roomFactory.createRoom( - DEFAULT_ROOM_DIMENSION.x, - DEFAULT_ROOM_DIMENSION.y, - DEFAULT_ROOM_DIMENSION.z, - ); - - const camera = new PerspectiveCamera(90, width / height, 0.001, 1000.0); - camera.position.fromArray(DEFAULT_CAMERA_POSITION); - - const light = new DirectionalLight(0xffffff, 4.0); - light.position.set(-1, 1, -1); - scene.add(light); - - // @TODO: only animate when headset pose change - const animate = () => { - requestAnimationFrame(animate); - const renderer = this.renderer; - if (!renderer) return; - - renderer.render(scene, camera); - const canvas = renderer.domElement; - var destCtx = canvas.getContext('2d'); - - if (this.canvas) { - destCtx.drawImage(this.appCanvas, 0, 0); - } - }; - - animate(); - - window.addEventListener( - 'resize', - (_event) => { - const width = window.innerWidth; - const height = window.innerHeight; - if (this.renderer) this.renderer.setSize(width, height); - camera.aspect = width / height; - camera.updateProjectionMatrix(); - }, - false, - ); - - this.scene = scene; - this.camera = camera; - this.raycaster = new Raycaster(); - this.hitTestTarget = new Object3D(); - this.hitTestMarker = new Object3D(); - this.hitTestMarker.rotateX(-Math.PI / 2); - this.hitTestTarget.add(this.hitTestMarker); - this.userObjects = {}; - } - - inject(canvasContainer) { - const appendCanvas = () => { - if (this.renderer) canvasContainer.appendChild(this.renderer.domElement); - }; - - if (document.body) { - appendCanvas(); - } else { - document.addEventListener('DOMContentLoaded', appendCanvas); - } - } - - eject() { - if (this.renderer) { - const element = this.renderer.domElement; - element.parentElement.remove(); - } - } - - setCanvas(canvas) { - this.appCanvas = canvas; - } - - updateCameraTransform(positionArray, quaternionArray) { - this.camera.position.fromArray(positionArray); - this.camera.quaternion.fromArray(quaternionArray); - } - - createRoom(dimension) { - this.roomFactory.createRoom(dimension.x, dimension.y, dimension.z); - } - - getHitTestResults(origin, direction) { - this.raycaster.set( - new Vector3().fromArray(origin), - new Vector3().fromArray(direction), - ); - const targets = []; - if (this.roomFactory.roomCollider) { - targets.push(this.roomFactory.roomCollider); - } - const intersects = this.raycaster.intersectObjects(targets, true); - - const results = []; - intersects.forEach((intersect) => { - this.hitTestTarget.position.copy(intersect.point); - this.hitTestTarget.lookAt( - new Vector3().addVectors(intersect.point, intersect.face.normal), - ); - this.hitTestTarget.updateWorldMatrix(false, true); - - results.push( - mat4.fromValues(...this.hitTestMarker.matrixWorld.toArray()), - ); - }); - return results; - } - - get xrPlanes() { - return [ - ...this.roomFactory.xrPlanes, - ...Object.values(this.userObjects) - .filter((object) => object.userData.type === 'plane') - .map((object) => object.userData.xrObjectRef), - ]; - } - - get xrMeshes() { - return new Set( - Object.values(this.userObjects) - .filter((object) => object.userData.type === 'mesh') - .map((object) => object.userData.xrObjectRef), - ); - } - - updateUserObjects(objects) { - // filter out hidden objects - [...Object.keys(objects)].forEach((userObjectId) => { - if (!objects[userObjectId].active) { - delete objects[userObjectId]; - } - }); - Object.entries(objects).forEach(([userObjectId, objectData]) => { - const { - type, - width, - height, - depth, - isVertical, - semanticLabel, - position, - quaternion, - } = objectData; - let object; - if (type === 'mesh') { - if (!this.userObjects[userObjectId]) { - const mesh = new Mesh( - new BoxGeometry(width, height, depth), - new MeshBasicMaterial({ color: 0xffffff * Math.random() }), - ); - mesh.userData = { type, semanticLabel }; - this.userObjects[userObjectId] = mesh; - this.scene.add(mesh); - mesh.userData.xrObjectRef = buildXRMesh(mesh); - } - object = this.userObjects[userObjectId]; - } else if (type === 'plane') { - if (!this.userObjects[userObjectId]) { - const planeGeometry = new PlaneGeometry(width, height); - planeGeometry.rotateX(Math.PI / 2); - const mesh = new Mesh( - planeGeometry, - new MeshBasicMaterial({ - color: 0xffffff * Math.random(), - side: DoubleSide, - }), - ); - mesh.userData = { type, semanticLabel }; - this.userObjects[userObjectId] = mesh; - this.scene.add(mesh); - mesh.userData.xrObjectRef = buildXRPlane( - width / 2, - height / 2, - position, - { - orientation: isVertical - ? XRPlaneOrientation.Vertical - : XRPlaneOrientation.Horizontal, - quaternion, - semanticLabel, - }, - ); - } - object = this.userObjects[userObjectId]; - } - if (object) { - object.position.fromArray(position); - object.quaternion.fromArray(quaternion); - object.userData.xrObjectRef._updateMatrix(position, quaternion); - } - }); - - Object.keys(this.userObjects) - .filter((key) => !Object.keys(objects).includes(key)) - .forEach((key) => { - this.userObjects[key].parent.remove(this.userObjects[key]); - delete this.userObjects[key]; - }); - } -} diff --git a/webxr-emulator/actions.js b/webxr-emulator/actions.js deleted file mode 100644 index b7a9911..0000000 --- a/webxr-emulator/actions.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -/** - * Events triggered by the emulator UI and sent to the content script - */ -export const EMULATOR_ACTIONS = { - HEADSET_POSE_CHANGE: 'ea-headset-pose-change', - CONTROLLER_POSE_CHANGE: 'ea-controller-pose-change', - CONTROLLER_VISIBILITY_CHANGE: 'ea-controller-visibility-change', - BUTTON_STATE_CHANGE: 'ea-button-state-change', - ANALOG_VALUE_CHANGE: 'ea-analog-value-change', - DEVICE_TYPE_CHANGE: 'ea-device-type-change', - STEREO_TOGGLE: 'ea-stereo-toggle', - KEYBOARD_EVENT: 'ea-keyboard-event', - EXIT_IMMERSIVE: 'ea-exit-immersive', - ROOM_DIMENSION_CHANGE: 'ea-room-dimension-change', - EXCLUDE_POLYFILL: 'ea-exclude-polyfill', - INPUT_MODE_CHANGE: 'ea-input-mode-change', - HAND_POSE_CHANGE: 'ea-hand-pose-change', - HAND_VISIBILITY_CHANGE: 'ea-hand-visibility-change', - PINCH_VALUE_CHANGE: 'ea-pinch-value-change', - USER_OBJECTS_CHANGE: 'ea-user-objects-change', -}; - -/** - * Events triggered by the content script and caught and processed by the custom WebXR Polyfill - */ -export const POLYFILL_ACTIONS = EMULATOR_ACTIONS -// { -// HEADSET_POSE_CHANGE: 'pa-headset-pose-change', -// CONTROLLER_POSE_CHANGE: 'pa-controller-pose-change', -// CONTROLLER_VISIBILITY_CHANGE: 'pa-controller-visibility-change', -// BUTTON_STATE_CHANGE: 'pa-button-state-change', -// ANALOG_VALUE_CHANGE: 'pa-analog-value-change', -// DEVICE_TYPE_CHANGE: 'pa-device-type-change', -// STEREO_TOGGLE: 'pa-stereo-toggle', -// KEYBOARD_EVENT: 'pa-keyboard-event', -// EXIT_IMMERSIVE: 'pa-exit-immersive', -// DEVICE_INIT: 'pa-device-init', -// ROOM_DIMENSION_CHANGE: 'pa-room-dimension-change', -// INPUT_MODE_CHANGE: 'pa-input-mode-change', -// HAND_POSE_CHANGE: 'pa-hand-pose-change', -// HAND_VISIBILITY_CHANGE: 'pa-hand-visibility-change', -// PINCH_VALUE_CHANGE: 'pa-pinch-value-change', -// USER_OBJECTS_CHANGE: 'pa-user-objects-change', -// }; - -/** - * Events triggered from the client side that are caught by the content script and then relayed back to the emulator side - */ -export const CLIENT_ACTIONS = { - ENTER_IMMERSIVE: 'ca-enter-immersive', - EXIT_IMMERSIVE: 'ca-exit-immersive', - PING: 'ca-ping', -}; diff --git a/webxr-emulator/api/XRAnchor.js b/webxr-emulator/api/XRAnchor.js deleted file mode 100644 index f269b3c..0000000 --- a/webxr-emulator/api/XRAnchor.js +++ /dev/null @@ -1,80 +0,0 @@ -import XRSpace from 'webxr-polyfill/src/api/XRSpace'; -import { generateUUID } from 'three/src/math/MathUtils'; -import localforage from 'localforage'; - -export const PRIVATE = Symbol('@@webxr-polyfill/XRAnchor'); - -const ANCHOR_DELETED_ERROR = - 'Unable to access anchor properties, the anchor was already deleted.'; - -export class XRAnchor { - /** - * @param {import('webxr-polyfill/src/api/XRSession').default} session - * @param {import('webxr-polyfill/src/api/XRSpace').default} anchorSpace - */ - constructor(session, anchorSpace) { - this[PRIVATE] = { - session, - anchorSpace, - }; - } - - /** - * @type {import('webxr-polyfill/src/api/XRSpace').default} - */ - get anchorSpace() { - if (this[PRIVATE].session.hasTrackedAnchor(this)) { - return this[PRIVATE].anchorSpace; - } else { - throw new DOMException(ANCHOR_DELETED_ERROR, 'InvalidStateError'); - } - } - - async requestPersistentHandle() { - const handle = await savePersistentAnchor(this); - await restorePersistentAnchors(this[PRIVATE].session); - return handle; - } - - delete() { - this[PRIVATE].session.deleteTrackedAnchor(this); - } -} - -export class XRAnchorSet extends Set {} - -export const savePersistentAnchor = async (anchor) => { - const existingUUID = anchor[PRIVATE].session.getPersistentAnchorUUID(anchor); - if (existingUUID) { - return existingUUID; - } - const prefix = window.location.hostname + PRIVATE.toString(); - const anchorHandle = generateUUID(); - const matrix = Array.from(anchor[PRIVATE].anchorSpace._baseMatrix); - await localforage.setItem( - prefix + anchorHandle, - JSON.stringify({ uuid: anchorHandle, matrixValue: matrix }), - ); - return anchorHandle; -}; - -export const deletePersistentAnchor = async (uuid) => { - const prefix = window.location.hostname + PRIVATE.toString(); - await localforage.removeItem(prefix + uuid); -}; - -export const restorePersistentAnchors = async (session) => { - session.persistentAnchorsMap = new Map(); - const prefix = window.location.hostname + PRIVATE.toString(); - const keys = (await localforage.keys()).filter((key) => - key.startsWith(prefix), - ); - keys.forEach(async (key) => { - const { uuid, matrixValue } = JSON.parse(await localforage.getItem(key)); - const matrix = new Float32Array(matrixValue); - const anchorSpace = new XRSpace(); - anchorSpace._baseMatrix = matrix; - const anchor = new XRAnchor(session, anchorSpace); - session.persistentAnchorsMap.set(uuid, anchor); - }); -}; diff --git a/webxr-emulator/api/XRGamepadInput.js b/webxr-emulator/api/XRGamepadInput.js deleted file mode 100644 index ba7370f..0000000 --- a/webxr-emulator/api/XRGamepadInput.js +++ /dev/null @@ -1,318 +0,0 @@ -import { mat4, quat, vec3 } from 'gl-matrix'; - -import GamepadMappings from 'webxr-polyfill/src/devices/GamepadMappings'; -import OrientationArmModel from 'webxr-polyfill/src/lib/OrientationArmModel'; -import XRInputSource from './XRInputSource'; -import XRPose from 'webxr-polyfill/src/api/XRPose'; -import XRRigidTransform from 'webxr-polyfill/src/api/XRRigidTransform'; - -export const PRIVATE = Symbol('@@webxr-polyfill/XRRemappedGamepad'); - -const PLACEHOLDER_BUTTON = { pressed: false, touched: false, value: 0.0 }; -Object.freeze(PLACEHOLDER_BUTTON); - -export class XRRemappedGamepad { - constructor(gamepad, display, map) { - if (!map) { - map = {}; - } - - // Apply user-agent-specific overrides to the mapping when applicable. - if (map.userAgentOverrides) { - for (const agent in map.userAgentOverrides) { - if (navigator.userAgent.includes(agent)) { - const override = map.userAgentOverrides[agent]; - - for (const key in override) { - if (key in map) { - // If the key already exists, merge the override values into the - // existing dictionary. - Object.assign(map[key], override[key]); - } else { - // If the base mapping doesn't have this key, insert the override - // values wholesale. - map[key] = override[key]; - } - } - break; - } - } - } - - const axes = new Array( - map.axes && map.axes.length ? map.axes.length : gamepad.axes.length, - ); - const buttons = new Array( - map.buttons && map.buttons.length - ? map.buttons.length - : gamepad.buttons.length, - ); - - let gripTransform = null; - if (map.gripTransform) { - const orientation = map.gripTransform.orientation || [0, 0, 0, 1]; - gripTransform = mat4.create(); - mat4.fromRotationTranslation( - gripTransform, - quat.normalize(orientation, orientation), - map.gripTransform.position || [0, 0, 0], - ); - } - - let targetRayTransform = null; - if (map.targetRayTransform) { - const orientation = map.targetRayTransform.orientation || [0, 0, 0, 1]; - targetRayTransform = mat4.create(); - mat4.fromRotationTranslation( - targetRayTransform, - quat.normalize(orientation, orientation), - map.targetRayTransform.position || [0, 0, 0], - ); - } - - let profiles = map.profiles; - if (map.displayProfiles) { - if (display.displayName in map.displayProfiles) { - profiles = map.displayProfiles[display.displayName]; - } - } - - this[PRIVATE] = { - gamepad, - map, - profiles: profiles || [gamepad.id], - mapping: map.mapping || gamepad.mapping, - axes, - buttons, - gripTransform, - targetRayTransform, - }; - - this._update(); - } - - _update() { - const gamepad = this[PRIVATE].gamepad; - const map = this[PRIVATE].map; - - const axes = this[PRIVATE].axes; - for (let i = 0; i < axes.length; ++i) { - if (map.axes && i in map.axes) { - if (map.axes[i] === null) { - axes[i] = 0; - } else { - axes[i] = gamepad.axes[map.axes[i]]; - } - } else { - axes[i] = gamepad.axes[i]; - } - } - - if (map.axes && map.axes.invert) { - for (const axis of map.axes.invert) { - if (axis < axes.length) { - axes[axis] *= -1; - } - } - } - - const buttons = this[PRIVATE].buttons; - for (let i = 0; i < buttons.length; ++i) { - if (map.buttons && i in map.buttons) { - if (map.buttons[i] === null) { - buttons[i] = PLACEHOLDER_BUTTON; - } else { - buttons[i] = gamepad.buttons[map.buttons[i]]; - } - } else { - buttons[i] = gamepad.buttons[i]; - } - } - } - - get id() { - return ''; - } - - get _profiles() { - return this[PRIVATE].profiles; - } - - get index() { - return -1; - } - - get connected() { - return this[PRIVATE].gamepad.connected; - } - - get timestamp() { - return this[PRIVATE].gamepad.timestamp; - } - - get mapping() { - return this[PRIVATE].mapping; - } - - get axes() { - return this[PRIVATE].axes; - } - - get buttons() { - return this[PRIVATE].buttons; - } - - // Non-standard extension - get hapticActuators() { - return this[PRIVATE].gamepad.hapticActuators; - } -} - -export default class GamepadXRInputSource { - constructor( - polyfill, - display, - primaryButtonIndex = 0, - primarySqueezeButtonIndex = -1, - ) { - this.polyfill = polyfill; - this.display = display; - this.nativeGamepad = null; - this.gamepad = null; - this.inputSource = new XRInputSource(this); - this.lastPosition = vec3.create(); - this.emulatedPosition = false; - this.basePoseMatrix = mat4.create(); - this.outputMatrix = mat4.create(); - this.primaryButtonIndex = primaryButtonIndex; - this.primaryActionPressed = false; - this.primarySqueezeButtonIndex = primarySqueezeButtonIndex; - this.primarySqueezeActionPressed = false; - this.handedness = ''; - this.targetRayMode = 'gaze'; - this.armModel = null; - this.profilesOverride = null; - } - - get profiles() { - if (this.profilesOverride) { - return this.profilesOverride; - } else { - return this.gamepad ? this.gamepad._profiles : []; - } - } - - updateFromGamepad(gamepad) { - if (this.nativeGamepad !== gamepad) { - this.nativeGamepad = gamepad; - if (gamepad) { - this.gamepad = new XRRemappedGamepad( - gamepad, - this.display, - GamepadMappings[gamepad.id], - ); - } else { - this.gamepad = null; - } - } - this.handedness = gamepad.hand === '' ? 'none' : gamepad.hand; - - if (this.gamepad) { - this.gamepad._update(); - } - - if (gamepad.pose) { - this.targetRayMode = 'tracked-pointer'; - this.emulatedPosition = !gamepad.pose.hasPosition; - } else if (gamepad.hand === '') { - this.targetRayMode = 'gaze'; - this.emulatedPosition = false; - } - } - - updateBasePoseMatrix() { - if (this.nativeGamepad && this.nativeGamepad.pose) { - const pose = this.nativeGamepad.pose; - let position = pose.position; - const orientation = pose.orientation; - // On initialization, we might not have any values - if (!position && !orientation) { - return; - } - if (!position) { - if (!pose.hasPosition) { - if (!this.armModel) { - this.armModel = new OrientationArmModel(); - } - - this.armModel.setHandedness(this.nativeGamepad.hand); - this.armModel.update(orientation, this.polyfill.getBasePoseMatrix()); - position = this.armModel.getPosition(); - } else { - position = this.lastPosition; - } - } else { - // This is if we temporarily lose tracking, so the controller doesn't - // snap back to the origin. - this.lastPosition[0] = position[0]; - this.lastPosition[1] = position[1]; - this.lastPosition[2] = position[2]; - } - mat4.fromRotationTranslation(this.basePoseMatrix, orientation, position); - } else { - mat4.copy(this.basePoseMatrix, this.polyfill.getBasePoseMatrix()); - } - return this.basePoseMatrix; - } - - /** - * @param {XRReferenceSpace} coordinateSystem - * @param {string} poseType - * @return {XRPose?} - */ - getXRPose(coordinateSystem, poseType) { - this.updateBasePoseMatrix(); - - switch (poseType) { - case 'target-ray': - coordinateSystem._transformBasePoseMatrix( - this.outputMatrix, - this.basePoseMatrix, - ); - if (this.gamepad && this.gamepad[PRIVATE].targetRayTransform) { - mat4.multiply( - this.outputMatrix, - this.outputMatrix, - this.gamepad[PRIVATE].targetRayTransform, - ); - } - break; - case 'grip': - if (!this.nativeGamepad || !this.nativeGamepad.pose) { - return null; - } - // TODO: Does the grip matrix need to be tweaked? - coordinateSystem._transformBasePoseMatrix( - this.outputMatrix, - this.basePoseMatrix, - ); - if (this.gamepad && this.gamepad[PRIVATE].gripTransform) { - mat4.multiply( - this.outputMatrix, - this.outputMatrix, - this.gamepad[PRIVATE].gripTransform, - ); - } - break; - default: - return null; - } - - coordinateSystem._adjustForOriginOffset(this.outputMatrix); - - return new XRPose( - new XRRigidTransform(this.outputMatrix), - this.emulatedPosition, - ); - } -} diff --git a/webxr-emulator/api/XRHand.js b/webxr-emulator/api/XRHand.js deleted file mode 100644 index 5c2564e..0000000 --- a/webxr-emulator/api/XRHand.js +++ /dev/null @@ -1,48 +0,0 @@ -import { XRJointSpace } from './XRJointSpace'; - -export const XRHandJoint = { - Wrist: 'wrist', - - ThumbMetacarpal: 'thumb-metacarpal', - ThumbPhalanxProximal: 'thumb-phalanx-proximal', - ThumbPhalanxDistal: 'thumb-phalanx-distal', - ThumbTip: 'thumb-tip', - - IndexFingerMetacarpal: 'index-finger-metacarpal', - IndexFingerPhalanxProximal: 'index-finger-phalanx-proximal', - IndexFingerPhalanxIntermediate: 'index-finger-phalanx-intermediate', - IndexFingerPhalanxDistal: 'index-finger-phalanx-distal', - IndexFingerTip: 'index-finger-tip', - - MiddleFingerMetacarpal: 'middle-finger-metacarpal', - MiddleFingerPhalanxProximal: 'middle-finger-phalanx-proximal', - MiddleFingerPhalanxIntermediate: 'middle-finger-phalanx-intermediate', - MiddleFingerPhalanxDistal: 'middle-finger-phalanx-distal', - MiddleFingerTip: 'middle-finger-tip', - - RingFingerMetacarpal: 'ring-finger-metacarpal', - RingFingerPhalanxProximal: 'ring-finger-phalanx-proximal', - RingFingerPhalanxIntermediate: 'ring-finger-phalanx-intermediate', - RingFingerPhalanxDistal: 'ring-finger-phalanx-distal', - RingFingerTip: 'ring-finger-tip', - - PinkyFingerMetacarpal: 'pinky-finger-metacarpal', - PinkyFingerPhalanxProximal: 'pinky-finger-phalanx-proximal', - PinkyFingerPhalanxIntermediate: 'pinky-finger-phalanx-intermediate', - PinkyFingerPhalanxDistal: 'pinky-finger-phalanx-distal', - PinkyFingerTip: 'pinky-finger-tip', -}; - -export const PRIVATE = Symbol('@@webxr-polyfill/XRHand'); - -export class XRHand extends Map { - constructor(inputSource) { - super(); - this[PRIVATE] = { - inputSource, - }; - Object.values(XRHandJoint).forEach((jointName) => { - this.set(jointName, new XRJointSpace(jointName, this)); - }); - } -} diff --git a/webxr-emulator/api/XRHandInput.js b/webxr-emulator/api/XRHandInput.js deleted file mode 100644 index 0d25424..0000000 --- a/webxr-emulator/api/XRHandInput.js +++ /dev/null @@ -1,139 +0,0 @@ -import { PRIVATE, XRRemappedGamepad } from './XRGamepadInput'; -import { mat4, vec3 } from 'gl-matrix'; - -import GamepadMappings from 'webxr-polyfill/src/devices/GamepadMappings'; -import { XRHand } from './XRHand'; -import XRInputSource from './XRInputSource'; -import XRPose from 'webxr-polyfill/src/api/XRPose'; -import XRRigidTransform from 'webxr-polyfill/src/api/XRRigidTransform'; - -export default class HandXRInputSource { - constructor(polyfill) { - this.polyfill = polyfill; - this.nativeGamepad = null; - this.gamepad = null; - this.inputSource = new XRInputSource(this); - this.lastPosition = vec3.create(); - this.emulatedPosition = false; - this.basePoseMatrix = mat4.create(); - this.outputMatrix = mat4.create(); - this.primaryActionPressed = false; - this.handedness = ''; - this.targetRayMode = 'gaze'; - this.armModel = null; - - this.hand = new XRHand(this.inputSource); - } - - get profiles() { - return [ - 'oculus-hand', - 'generic-hand', - 'generic-hand-select', - 'generic-trigger', - ]; - } - - updateFromGamepad(gamepad, pinchValue) { - if (this.nativeGamepad !== gamepad) { - this.nativeGamepad = gamepad; - if (gamepad) { - this.gamepad = new XRRemappedGamepad( - gamepad, - {}, - GamepadMappings[gamepad.id], - ); - } else { - this.gamepad = null; - } - } - this.handedness = gamepad.hand === '' ? 'none' : gamepad.hand; - - if (this.gamepad) { - this.gamepad._update(); - this.gamepad[PRIVATE].buttons = [ - { - pressed: pinchValue == 1, - touched: pinchValue > 0, - value: pinchValue, - }, - ]; - this.gamepad[PRIVATE].axes = []; - } - - if (gamepad.pose) { - this.targetRayMode = 'tracked-pointer'; - this.emulatedPosition = !gamepad.pose.hasPosition; - } else if (gamepad.hand === '') { - this.targetRayMode = 'gaze'; - this.emulatedPosition = false; - } - } - - updateBasePoseMatrix() { - if (this.nativeGamepad && this.nativeGamepad.pose) { - const pose = this.nativeGamepad.pose; - const position = pose.position; - const orientation = pose.orientation; - // On initialization, we might not have any values - if (!position && !orientation) { - return; - } - mat4.fromRotationTranslation(this.basePoseMatrix, orientation, position); - } else { - mat4.copy(this.basePoseMatrix, this.polyfill.getBasePoseMatrix()); - } - return this.basePoseMatrix; - } - - /** - * @param {XRReferenceSpace} coordinateSystem - * @param {string} poseType - * @return {XRPose?} - */ - getXRPose(coordinateSystem, poseType) { - this.updateBasePoseMatrix(); - - switch (poseType) { - case 'target-ray': - coordinateSystem._transformBasePoseMatrix( - this.outputMatrix, - this.basePoseMatrix, - ); - if (this.gamepad && this.gamepad[PRIVATE].targetRayTransform) { - mat4.multiply( - this.outputMatrix, - this.outputMatrix, - this.gamepad[PRIVATE].targetRayTransform, - ); - } - break; - case 'grip': - if (!this.nativeGamepad || !this.nativeGamepad.pose) { - return null; - } - // TODO: Does the grip matrix need to be tweaked? - coordinateSystem._transformBasePoseMatrix( - this.outputMatrix, - this.basePoseMatrix, - ); - if (this.gamepad && this.gamepad[PRIVATE].gripTransform) { - mat4.multiply( - this.outputMatrix, - this.outputMatrix, - this.gamepad[PRIVATE].gripTransform, - ); - } - break; - default: - return null; - } - - coordinateSystem._adjustForOriginOffset(this.outputMatrix); - - return new XRPose( - new XRRigidTransform(this.outputMatrix), - this.emulatedPosition, - ); - } -} diff --git a/webxr-emulator/api/XRHitTestResult.js b/webxr-emulator/api/XRHitTestResult.js deleted file mode 100644 index bc61bf6..0000000 --- a/webxr-emulator/api/XRHitTestResult.js +++ /dev/null @@ -1,36 +0,0 @@ -export const PRIVATE = Symbol('@@webxr-polyfill/XRHitTestResult'); - -import { XRAnchor } from './XRAnchor'; -import { PRIVATE as XRFRAME_PRIVATE } from 'webxr-polyfill/src/api/XRFrame'; -import XRSpace from 'webxr-polyfill/src/api/XRSpace'; -import { mat4 } from 'gl-matrix'; - -export default class XRHitTestResult { - constructor(frame, transform) { - this[PRIVATE] = { - frame, - transform, - }; - } - - getPose(baseSpace) { - const space = new XRSpace(); - space._baseMatrix = mat4.copy( - mat4.create(), - this[PRIVATE].transform.matrix, - ); - return this[PRIVATE].frame.getPose(space, baseSpace); - } - - async createAnchor() { - const anchorSpace = new XRSpace(); - anchorSpace._baseMatrix = mat4.copy( - mat4.create(), - this[PRIVATE].transform.matrix, - ); - const session = this[PRIVATE].frame[XRFRAME_PRIVATE].session; - const anchor = new XRAnchor(session, anchorSpace); - session.addTrackedAnchor(anchor); - return anchor; - } -} diff --git a/webxr-emulator/api/XRHitTestSource.js b/webxr-emulator/api/XRHitTestSource.js deleted file mode 100644 index ed5026f..0000000 --- a/webxr-emulator/api/XRHitTestSource.js +++ /dev/null @@ -1,39 +0,0 @@ -export const PRIVATE = Symbol('@@webxr-polyfill/XRHitTestSource'); - -import XRRay from './XRRay'; - -export default class XRHitTestSource { - constructor(session, options) { - // @TODO: Support options.entityTypes and options.offsetRay - // if (options.entityTypes && options.entityTypes.length > 0) { - // throw new Error('XRHitTestSource does not support entityTypes option yet.'); - // } - this[PRIVATE] = { - session, - space: options.space, - offsetRay: options.offsetRay || new XRRay(), - active: true - }; - } - - cancel() { - // @TODO: Throw InvalidStateError if active is already false - this[PRIVATE].active = false; - } - - get _space() { - return this[PRIVATE].space; - } - - get _session() { - return this[PRIVATE].session; - } - - get _offsetRay() { - return this[PRIVATE].offsetRay; - } - - get _active() { - return this[PRIVATE].active; - } -} diff --git a/webxr-emulator/api/XRInputSource.js b/webxr-emulator/api/XRInputSource.js deleted file mode 100644 index cf93268..0000000 --- a/webxr-emulator/api/XRInputSource.js +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2018 Google Inc. All Rights Reserved. - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import XRSpace from 'webxr-polyfill/src/api/XRSpace'; - -export const PRIVATE = Symbol('@@webxr-polyfill/XRInputSource'); - -export default class XRInputSource { - /** - * @param {GamepadXRInputSource} impl - */ - constructor(impl) { - this[PRIVATE] = { - impl, - gripSpace: new XRSpace('grip', this), - targetRaySpace: new XRSpace('target-ray', this), - }; - } - - /** - * @return {XRHandedness} - */ - get handedness() { - return this[PRIVATE].impl.handedness; - } - - /** - * @return {XRTargetRayMode} - */ - get targetRayMode() { - return this[PRIVATE].impl.targetRayMode; - } - - /** - * @return {XRSpace} - */ - get gripSpace() { - const mode = this[PRIVATE].impl.targetRayMode; - if (mode === 'gaze' || mode === 'screen') { - // grip space must be null for non-trackable input sources - return null; - } - return this[PRIVATE].gripSpace; - } - - /** - * @return {XRSpace} - */ - get targetRaySpace() { - return this[PRIVATE].targetRaySpace; - } - - /** - * @return {Array} - */ - get profiles() { - return this[PRIVATE].impl.profiles; - } - - /** - * @return {Gamepad} - */ - get gamepad() { - return this[PRIVATE].impl.gamepad; - } - - /** - * @return {XRHand} - */ - get hand() { - return this[PRIVATE].impl.hand; - } -} diff --git a/webxr-emulator/api/XRJointPose.js b/webxr-emulator/api/XRJointPose.js deleted file mode 100644 index 83a54c5..0000000 --- a/webxr-emulator/api/XRJointPose.js +++ /dev/null @@ -1,16 +0,0 @@ -import XRPose from 'webxr-polyfill/src/api/XRPose'; - -export const PRIVATE = Symbol('@@webxr-polyfill/XRJointPose'); - -export class XRJointPose extends XRPose { - constructor(transform, radius) { - super(transform); - this[PRIVATE] = { - radius, - }; - } - - get radius() { - return this[PRIVATE].radius; - } -} diff --git a/webxr-emulator/api/XRJointSpace.js b/webxr-emulator/api/XRJointSpace.js deleted file mode 100644 index c0c31a9..0000000 --- a/webxr-emulator/api/XRJointSpace.js +++ /dev/null @@ -1,17 +0,0 @@ -import XRSpace from 'webxr-polyfill/src/api/XRSpace'; - -export const PRIVATE = Symbol('@@webxr-polyfill/XRJointSpace'); - -export class XRJointSpace extends XRSpace { - constructor(jointName, xrhand) { - super(); - this[PRIVATE] = { - jointName, - xrhand, - }; - } - - get jointName() { - return this[PRIVATE].jointName; - } -} diff --git a/webxr-emulator/api/XRMesh.js b/webxr-emulator/api/XRMesh.js deleted file mode 100644 index 90026d1..0000000 --- a/webxr-emulator/api/XRMesh.js +++ /dev/null @@ -1,73 +0,0 @@ -import { mat4 } from 'gl-matrix'; - -/** - * @see https://immersive-web.github.io/real-world-meshing/ - */ -export class XRMesh { - /** - * @param {import('webxr-polyfill/src/api/XRSpace').default} planeSpace - * @param {Float32Array} pointArray - * @param {Float32Array} indexArray - * @param {string} semanticLabel - */ - constructor(meshSpace, vertices, indices, semanticLabel) { - this._meshSpace = meshSpace; - this._vertices = vertices; - this._indices = indices; - this._lastChangedTime = performance.now(); - this._semanticLabel = semanticLabel; - } - - /** - * @type {import('webxr-polyfill/src/api/XRSpace').default} - * @readonly - */ - get meshSpace() { - return this._meshSpace; - } - - /** - * @type {Float32Array} - * @readonly - */ - get vertices() { - return this._vertices; - } - - /** - * @type {Float32Array} - * @readonly - */ - get indices() { - return this._indices; - } - - /** - * @type {DOMHighResTimeStamp} - * @readonly - */ - get lastChangedTime() { - return this._lastChangedTime; - } - - /** - * @type {string} - * @readonly - */ - get semanticLabel() { - return this._semanticLabel; - } - - /** - * non-standard - * @param {number[]} position - * @param {number[]} quaternion - */ - _updateMatrix(position, quaternion) { - const meshMatrix = new Float32Array(16); - mat4.fromRotationTranslation(meshMatrix, quaternion, position); - this._meshSpace._baseMatrix = meshMatrix; - } -} - -export class XRMeshSet extends Set {} diff --git a/webxr-emulator/api/XRPlane.js b/webxr-emulator/api/XRPlane.js deleted file mode 100644 index c932e2b..0000000 --- a/webxr-emulator/api/XRPlane.js +++ /dev/null @@ -1,83 +0,0 @@ -import { mat4 } from 'gl-matrix'; - -/** - * @see https://immersive-web.github.io/real-world-geometry/plane-detection.html#plane-orientation - * @enum {string} - */ -export const XRPlaneOrientation = { - Horizontal: 'horizontal', - Vertical: 'vertical', -}; - -/** - * @see https://immersive-web.github.io/real-world-geometry/plane-detection.html#plane - */ -export class XRPlane { - /** - * @param {import('webxr-polyfill/src/api/XRSpace').default} planeSpace - * @param {DOMPointReadOnly[]} pointArray - * @param {XRPlaneOrientation} orientation - * @param {string} semanticLabel - */ - constructor(planeSpace, pointArray, orientation, semanticLabel) { - this._planeSpace = planeSpace; - this._polygon = pointArray; - Object.freeze(this._polygon); - this._orientation = orientation; - this._lastChangedTime = performance.now(); - this._semanticLabel = semanticLabel; - } - - /** - * @type {import('webxr-polyfill/src/api/XRSpace').default} - * @readonly - */ - get planeSpace() { - return this._planeSpace; - } - - /** - * @type {DOMPointReadOnly[]} - * @readonly - */ - get polygon() { - return this._polygon; - } - - /** - * @type {XRPlaneOrientation} - * @readonly - */ - get orientation() { - return this._orientation; - } - - /** - * @type {DOMHighResTimeStamp} - * @readonly - */ - get lastChangedTime() { - return this._lastChangedTime; - } - - /** - * @type {string} - * @readonly - */ - get semanticLabel() { - return this._semanticLabel; - } - - /** - * non-standard - * @param {number[]} position - * @param {number[]} quaternion - */ - _updateMatrix(position, quaternion) { - const meshMatrix = new Float32Array(16); - mat4.fromRotationTranslation(meshMatrix, quaternion, position); - this._planeSpace._baseMatrix = meshMatrix; - } -} - -export class XRPlaneSet extends Set {} diff --git a/webxr-emulator/api/XRRay.js b/webxr-emulator/api/XRRay.js deleted file mode 100644 index 10b1992..0000000 --- a/webxr-emulator/api/XRRay.js +++ /dev/null @@ -1,120 +0,0 @@ -export const PRIVATE = Symbol('@@webxr-polyfill/XRRay'); - -import { mat4, vec3, vec4 } from 'gl-matrix'; - -import XRRigidTransform from 'webxr-polyfill/src/api/XRRigidTransform'; - -export default class XRRay { - constructor(origin, direction) { - const _origin = { x: 0, y: 0, z: 0, w: 1 }; - const _direction = { x: 0, y: 0, z: -1, w: 0 }; - - if (origin && origin instanceof XRRigidTransform) { - const transform = origin; - const matrix = transform.matrix; - const originVec4 = vec4.set( - vec4.create(), - _origin.x, - _origin.y, - _origin.z, - _origin.w, - ); - const directionVec4 = vec4.set( - vec4.create(), - _direction.x, - _direction.y, - _direction.z, - _direction.w, - ); - vec4.transformMat4(originVec4, originVec4, matrix); - vec4.transformMat4(directionVec4, directionVec4, matrix); - _origin.x = originVec4[0]; - _origin.y = originVec4[1]; - _origin.z = originVec4[2]; - _origin.w = originVec4[3]; - _direction.x = directionVec4[0]; - _direction.y = directionVec4[1]; - _direction.z = directionVec4[2]; - _direction.w = directionVec4[3]; - } else { - if (origin) { - _origin.x = origin.x; - _origin.y = origin.y; - _origin.z = origin.z; - _origin.w = origin.w; - } - if (direction) { - _direction.x = direction.x; - _direction.y = direction.y; - _direction.z = direction.z; - _direction.w = direction.w; - } - } - - // Normalize direction - const length = - Math.sqrt( - _direction.x * _direction.x + - _direction.y * _direction.y + - _direction.z * _direction.z, - ) || 1; - _direction.x = _direction.x / length; - _direction.y = _direction.y / length; - _direction.z = _direction.z / length; - - this[PRIVATE] = { - origin: new DOMPointReadOnly(_origin.x, _origin.y, _origin.z, _origin.w), - direction: new DOMPointReadOnly( - _direction.x, - _direction.y, - _direction.z, - _direction.w, - ), - matrix: null, - }; - } - - get origin() { - return this[PRIVATE].origin; - } - - get direction() { - return this[PRIVATE].direction; - } - - get matrix() { - if (this[PRIVATE].matrix) { - return this[PRIVATE].matrix; - } - // @TODO: Check if the calculation is correct - const z = vec3.set(vec3.create(), 0, 0, -1); - const origin = vec3.set( - vec3.create(), - this[PRIVATE].origin.x, - this[PRIVATE].origin.y, - this[PRIVATE].origin.z, - ); - const direction = vec3.set( - vec3.create(), - this[PRIVATE].direction.x, - this[PRIVATE].direction.y, - this[PRIVATE].direction.z, - ); - const axis = vec3.cross(vec3.create(), direction, z); - const cosAngle = vec3.dot(direction, z); - const rotation = mat4.create(); - if (cosAngle > -1 && cosAngle < 1) { - mat4.fromRotation(rotation, Math.acos(cosAngle), axis); - } else if (cosAngle === -1) { - mat4.fromRotation( - rotation, - Math.acos(cosAngle), - vec3.set(vec3.create(), 1, 0, 0), - ); - } - const translation = mat4.fromTranslation(mat4.create(), origin); - const matrix = mat4.multiply(mat4.create(), translation, rotation); - this[PRIVATE].matrix = matrix; - return matrix; - } -} diff --git a/webxr-emulator/api/XRTransientInputHitTestResult.js b/webxr-emulator/api/XRTransientInputHitTestResult.js deleted file mode 100644 index f66ea7d..0000000 --- a/webxr-emulator/api/XRTransientInputHitTestResult.js +++ /dev/null @@ -1,19 +0,0 @@ -export const PRIVATE = Symbol('@@webxr-polyfill/XRTransientInputHitTestResult'); - -export default class XRTransientInputHitTestResult { - constructor(frame, results, inputSource) { - this[PRIVATE] = { - frame, - inputSource, - results, - }; - } - - get inputSource() { - return this[PRIVATE].inputSource; - } - - get results() { - return this[PRIVATE].results; - } -} diff --git a/webxr-emulator/api/XRTransientInputHitTestSource.js b/webxr-emulator/api/XRTransientInputHitTestSource.js deleted file mode 100644 index f7a2eca..0000000 --- a/webxr-emulator/api/XRTransientInputHitTestSource.js +++ /dev/null @@ -1,41 +0,0 @@ -export const PRIVATE = Symbol('@@webxr-polyfill/XRTransientInputHitTestSource'); - -import XRRay from './XRRay'; - -export default class XRTransientInputHitTestSource { - constructor(session, options) { - // @TODO: Support options.entityTypes and options.offsetRay - if (options.entityTypes && options.entityTypes.length > 0) { - throw new Error( - 'XRHitTestSource does not support entityTypes option yet.', - ); - } - this[PRIVATE] = { - session, - profile: options.profile, - offsetRay: options.offsetRay || new XRRay(), - active: true, - }; - } - - cancel() { - // @TODO: Throw InvalidStateError if active is already false - this[PRIVATE].active = false; - } - - get _profile() { - return this[PRIVATE].profile; - } - - get _session() { - return this[PRIVATE].session; - } - - get _offsetRay() { - return this[PRIVATE].offsetRay; - } - - get _active() { - return this[PRIVATE].active; - } -} diff --git a/webxr-emulator/api/handPose.js b/webxr-emulator/api/handPose.js deleted file mode 100644 index caf34df..0000000 --- a/webxr-emulator/api/handPose.js +++ /dev/null @@ -1,669 +0,0 @@ -export const handPose = { - wrist: { - radius: 0.021460847929120064, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.037, 0.065, 0.002, 1], - }, - 'thumb-metacarpal': { - radius: 0.019382517784833908, - transform: [ - 0.262, -0.498, 0.827, 0, 0.912, 0.408, -0.043, 0, -0.315, 0.765, 0.561, 0, - -0.018, 0.03, -0.026, 1, - ], - }, - 'thumb-phalanx-proximal': { - radius: 0.01228295173496008, - transform: [ - 0.306, -0.143, 0.941, 0, 0.9, 0.367, -0.237, 0, -0.311, 0.919, 0.241, 0, - -0.008, 0.005, -0.045, 1, - ], - }, - 'thumb-phalanx-distal': { - radius: 0.009768804535269737, - transform: [ - 0.083, -0.018, 0.996, 0, 0.892, 0.448, -0.066, 0, -0.445, 0.894, 0.054, 0, - 0.002, -0.026, -0.053, 1, - ], - }, - 'thumb-tip': { - radius: 0.008768804371356964, - transform: [ - 0.083, -0.018, 0.996, 0, 0.892, 0.448, -0.066, 0, -0.445, 0.894, 0.054, 0, - 0.013, -0.049, -0.055, 1, - ], - }, - 'index-finger-metacarpal': { - radius: 0.021228281781077385, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.026, 0.028, -0.017, 1], - }, - 'index-finger-phalanx-proximal': { - radius: 0.010295259766280651, - transform: [ - 0.066, -0.039, -0.997, 0, -0.981, -0.184, -0.058, 0, -0.181, 0.982, -0.05, - 0, -0.029, -0.031, -0.021, 1, - ], - }, - 'index-finger-phalanx-intermediate': { - radius: 0.00853810179978609, - transform: [ - 0.018, -0.062, -0.998, 0, -0.914, -0.406, 0.008, 0, -0.406, 0.912, -0.064, - 0, -0.022, -0.068, -0.019, 1, - ], - }, - 'index-finger-phalanx-distal': { - radius: 0.007636196445673704, - transform: [ - 0.01, -0.122, -0.992, 0, -0.896, -0.442, 0.045, 0, -0.444, 0.888, -0.114, - 0, -0.013, -0.09, -0.018, 1, - ], - }, - 'index-finger-tip': { - radius: 0.006636196281760931, - transform: [ - 0.01, -0.122, -0.992, 0, -0.896, -0.442, 0.045, 0, -0.444, 0.888, -0.114, - 0, -0.004, -0.11, -0.015, 1, - ], - }, - 'middle-finger-metacarpal': { - radius: 0.021231964230537415, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.028, 0.03, -0.001, 1], - }, - 'middle-finger-phalanx-proximal': { - radius: 0.01117393933236599, - transform: [ - -0.009, -0.166, -0.986, 0, -0.945, -0.322, 0.063, 0, -0.328, 0.932, - -0.154, 0, -0.034, -0.03, 0.001, 1, - ], - }, - 'middle-finger-phalanx-intermediate': { - radius: 0.008030958473682404, - transform: [ - -0.027, -0.182, -0.983, 0, -0.87, -0.48, 0.113, 0, -0.493, 0.858, -0.145, - 0, -0.02, -0.07, 0.007, 1, - ], - }, - 'middle-finger-phalanx-distal': { - radius: 0.007629410829395056, - transform: [ - -0.099, -0.222, -0.97, 0, -0.885, -0.426, 0.188, 0, -0.455, 0.877, -0.154, - 0, -0.006, -0.094, 0.011, 1, - ], - }, - 'middle-finger-tip': { - radius: 0.006629410665482283, - transform: [ - -0.099, -0.222, -0.97, 0, -0.885, -0.426, 0.188, 0, -0.455, 0.877, -0.154, - 0, 0.004, -0.116, 0.016, 1, - ], - }, - 'ring-finger-metacarpal': { - radius: 0.019088275730609894, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.031, 0.03, 0.017, 1], - }, - 'ring-finger-phalanx-proximal': { - radius: 0.00992213748395443, - transform: [ - -0.088, -0.279, -0.956, 0, -0.919, -0.347, 0.186, 0, -0.384, 0.895, - -0.226, 0, -0.03, -0.023, 0.02, 1, - ], - }, - 'ring-finger-phalanx-intermediate': { - radius: 0.007611672393977642, - transform: [ - -0.15, -0.308, -0.939, 0, -0.844, -0.454, 0.284, 0, -0.514, 0.836, -0.192, - 0, -0.015, -0.058, 0.029, 1, - ], - }, - 'ring-finger-phalanx-distal': { - radius: 0.007231088820844889, - transform: [ - -0.194, -0.266, -0.944, 0, -0.79, -0.528, 0.311, 0, -0.582, 0.806, -0.108, - 0, -0.001, -0.081, 0.034, 1, - ], - }, - 'ring-finger-tip': { - radius: 0.0062310886569321156, - transform: [ - -0.194, -0.266, -0.944, 0, -0.79, -0.528, 0.311, 0, -0.582, 0.806, -0.108, - 0, 0.012, -0.101, 0.037, 1, - ], - }, - 'pinky-finger-metacarpal': { - radius: 0.01808827556669712, - transform: [ - -0.396, -0.279, -0.875, 0, -0.914, 0.023, 0.406, 0, -0.094, 0.96, -0.264, - 0, -0.027, 0.031, 0.025, 1, - ], - }, - 'pinky-finger-phalanx-proximal': { - radius: 0.008483353070914745, - transform: [ - -0.225, -0.362, -0.905, 0, -0.881, -0.321, 0.348, 0, -0.417, 0.875, - -0.246, 0, -0.023, -0.013, 0.037, 1, - ], - }, - 'pinky-finger-phalanx-intermediate': { - radius: 0.0067641944624483585, - transform: [ - -0.263, -0.461, -0.847, 0, -0.772, -0.426, 0.472, 0, -0.579, 0.778, - -0.244, 0, -0.01, -0.04, 0.045, 1, - ], - }, - 'pinky-finger-phalanx-distal': { - radius: 0.0064259846694767475, - transform: [ - -0.318, -0.383, -0.867, 0, -0.762, -0.441, 0.474, 0, -0.564, 0.812, - -0.151, 0, 0.002, -0.055, 0.05, 1, - ], - }, - 'pinky-finger-tip': { - radius: 0.005425984505563974, - transform: [ - -0.318, -0.383, -0.867, 0, -0.762, -0.441, 0.474, 0, -0.564, 0.812, - -0.151, 0, 0.013, -0.074, 0.054, 1, - ], - }, -}; - -export const pinchHandPose = { - wrist: { - radius: 0.021460847929120064, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.037, 0.066, 0, 1], - }, - 'thumb-metacarpal': { - radius: 0.019382517784833908, - transform: [ - -0.095, -0.306, 0.947, 0, 0.764, 0.587, 0.266, 0, -0.638, 0.749, 0.178, 0, - -0.013, 0.028, -0.021, 1, - ], - }, - 'thumb-phalanx-proximal': { - radius: 0.01228295173496008, - transform: [ - -0.141, 0.041, 0.989, 0, 0.85, 0.517, 0.1, 0, -0.508, 0.855, -0.108, 0, - 0.008, 0.003, -0.026, 1, - ], - }, - 'thumb-phalanx-distal': { - radius: 0.009768804535269737, - transform: [ - -0.338, 0.052, 0.94, 0, 0.751, 0.616, 0.237, 0, -0.567, 0.786, -0.247, 0, - 0.025, -0.026, -0.023, 1, - ], - }, - 'thumb-tip': { - radius: 0.008768804371356964, - transform: [ - -0.338, 0.052, 0.94, 0, 0.751, 0.616, 0.237, 0, -0.567, 0.786, -0.247, 0, - 0.039, -0.045, -0.018, 1, - ], - }, - 'index-finger-metacarpal': { - radius: 0.021228281781077385, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.024, 0.028, -0.015, 1], - }, - 'index-finger-phalanx-proximal': { - radius: 0.010295259766280651, - transform: [ - 0.074, 0.097, -0.993, 0, -0.737, -0.665, -0.12, 0, -0.672, 0.74, 0.022, 0, - -0.03, -0.03, -0.023, 1, - ], - }, - 'index-finger-phalanx-intermediate': { - radius: 0.00853810179978609, - transform: [ - 0.046, 0.054, -0.997, 0, 0.161, -0.986, -0.046, 0, -0.986, -0.158, -0.054, - 0, -0.005, -0.058, -0.024, 1, - ], - }, - 'index-finger-phalanx-distal': { - radius: 0.007636196445673704, - transform: [ - 0.1, 0.037, -0.994, 0, 0.497, -0.867, 0.018, 0, -0.862, -0.496, -0.105, 0, - 0.019, -0.054, -0.023, 1, - ], - }, - 'index-finger-tip': { - radius: 0.006636196281760931, - transform: [ - 0.1, 0.037, -0.994, 0, 0.497, -0.867, 0.018, 0, -0.862, -0.496, -0.105, 0, - 0.039, -0.044, -0.02, 1, - ], - }, - 'middle-finger-metacarpal': { - radius: 0.021231964230537415, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.028, 0.03, 0.001, 1], - }, - 'middle-finger-phalanx-proximal': { - radius: 0.01117393933236599, - transform: [ - -0.014, -0.044, -0.999, 0, -0.96, -0.279, 0.026, 0, -0.28, 0.959, -0.038, - 0, -0.035, -0.03, -0.001, 1, - ], - }, - 'middle-finger-phalanx-intermediate': { - radius: 0.008030958473682404, - transform: [ - -0.034, -0.059, -0.998, 0, -0.773, -0.631, 0.064, 0, -0.634, 0.773, - -0.024, 0, -0.023, -0.071, 0, 1, - ], - }, - 'middle-finger-phalanx-distal': { - radius: 0.007629410829395056, - transform: [ - -0.1, -0.114, -0.988, 0, -0.775, -0.614, 0.149, 0, -0.624, 0.781, -0.027, - 0, -0.005, -0.092, 0.001, 1, - ], - }, - 'middle-finger-tip': { - radius: 0.006629410665482283, - transform: [ - -0.1, -0.114, -0.988, 0, -0.775, -0.614, 0.149, 0, -0.624, 0.781, -0.027, - 0, 0.009, -0.112, 0.002, 1, - ], - }, - 'ring-finger-metacarpal': { - radius: 0.019088275730609894, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.031, 0.031, 0.015, 1], - }, - 'ring-finger-phalanx-proximal': { - radius: 0.00992213748395443, - transform: [ - -0.095, -0.202, -0.975, 0, -0.972, -0.193, 0.135, 0, -0.215, 0.96, -0.178, - 0, -0.031, -0.023, 0.018, 1, - ], - }, - 'ring-finger-phalanx-intermediate': { - radius: 0.007611672393977642, - transform: [ - -0.166, -0.223, -0.961, 0, -0.825, -0.503, 0.259, 0, -0.541, 0.835, -0.1, - 0, -0.022, -0.06, 0.025, 1, - ], - }, - 'ring-finger-phalanx-distal': { - radius: 0.007231088820844889, - transform: [ - -0.213, -0.183, -0.96, 0, -0.748, -0.601, 0.28, 0, -0.628, 0.778, -0.009, - 0, -0.008, -0.082, 0.027, 1, - ], - }, - 'ring-finger-tip': { - radius: 0.0062310886569321156, - transform: [ - -0.213, -0.183, -0.96, 0, -0.748, -0.601, 0.28, 0, -0.628, 0.778, -0.009, - 0, 0.006, -0.102, 0.028, 1, - ], - }, - 'pinky-finger-metacarpal': { - radius: 0.01808827556669712, - transform: [ - -0.396, -0.279, -0.875, 0, -0.914, 0.023, 0.406, 0, -0.094, 0.96, -0.264, - 0, -0.028, 0.032, 0.023, 1, - ], - }, - 'pinky-finger-phalanx-proximal': { - radius: 0.008483353070914745, - transform: [ - -0.219, -0.358, -0.908, 0, -0.933, -0.194, 0.302, 0, -0.284, 0.913, - -0.291, 0, -0.024, -0.012, 0.035, 1, - ], - }, - 'pinky-finger-phalanx-intermediate': { - radius: 0.0067641944624483585, - transform: [ - -0.277, -0.452, -0.848, 0, -0.784, -0.404, 0.472, 0, -0.556, 0.795, - -0.242, 0, -0.015, -0.04, 0.044, 1, - ], - }, - 'pinky-finger-phalanx-distal': { - radius: 0.0064259846694767475, - transform: [ - -0.333, -0.373, -0.866, 0, -0.722, -0.49, 0.489, 0, -0.607, 0.788, -0.106, - 0, -0.004, -0.056, 0.049, 1, - ], - }, - 'pinky-finger-tip': { - radius: 0.005425984505563974, - transform: [ - -0.333, -0.373, -0.866, 0, -0.722, -0.49, 0.489, 0, -0.607, 0.788, -0.106, - 0, 0.009, -0.074, 0.052, 1, - ], - }, -}; - -export const pointHandPose = { - wrist: { - radius: 0.021460847929120064, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.037, 0.066, 0.002, 1], - }, - 'thumb-metacarpal': { - radius: 0.019382517784833908, - transform: [ - 0.105, -0.416, 0.903, 0, 0.84, 0.524, 0.144, 0, -0.533, 0.743, 0.405, 0, - -0.016, 0.029, -0.024, 1, - ], - }, - 'thumb-phalanx-proximal': { - radius: 0.01228295173496008, - transform: [ - -0.115, 0.252, 0.961, 0, 0.88, 0.475, -0.019, 0, -0.461, 0.843, -0.276, 0, - 0.002, 0.004, -0.037, 1, - ], - }, - 'thumb-phalanx-distal': { - radius: 0.009768804535269737, - transform: [ - -0.531, 0.705, 0.47, 0, 0.815, 0.577, 0.056, 0, -0.232, 0.413, -0.881, 0, - 0.017, -0.024, -0.028, 1, - ], - }, - 'thumb-tip': { - radius: 0.008768804371356964, - transform: [ - -0.531, 0.705, 0.47, 0, 0.815, 0.577, 0.056, 0, -0.232, 0.413, -0.881, 0, - 0.023, -0.035, -0.007, 1, - ], - }, - 'index-finger-metacarpal': { - radius: 0.021228281781077385, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.025, 0.028, -0.016, 1], - }, - 'index-finger-phalanx-proximal': { - radius: 0.010295259766280651, - transform: [ - 0.066, -0.02, -0.998, 0, -0.976, -0.211, -0.06, 0, -0.21, 0.977, -0.033, - 0, -0.03, -0.03, -0.022, 1, - ], - }, - 'index-finger-phalanx-intermediate': { - radius: 0.00853810179978609, - transform: [ - 0.019, -0.045, -0.999, 0, -0.928, -0.374, -0.001, 0, -0.373, 0.926, - -0.049, 0, -0.022, -0.067, -0.021, 1, - ], - }, - 'index-finger-phalanx-distal': { - radius: 0.007636196445673704, - transform: [ - 0.006, -0.106, -0.994, 0, -0.956, -0.294, 0.025, 0, -0.295, 0.95, -0.103, - 0, -0.013, -0.09, -0.02, 1, - ], - }, - 'index-finger-tip': { - radius: 0.006636196281760931, - transform: [ - 0.006, -0.106, -0.994, 0, -0.956, -0.294, 0.025, 0, -0.295, 0.95, -0.103, - 0, -0.007, -0.111, -0.017, 1, - ], - }, - 'middle-finger-metacarpal': { - radius: 0.021231964230537415, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.028, 0.03, 0, 1], - }, - 'middle-finger-phalanx-proximal': { - radius: 0.01117393933236599, - transform: [ - -0.006, -0.157, -0.988, 0, -0.314, -0.937, 0.151, 0, -0.949, 0.311, - -0.044, 0, -0.034, -0.03, 0, 1, - ], - }, - 'middle-finger-phalanx-intermediate': { - radius: 0.008030958473682404, - transform: [ - -0.003, -0.183, -0.983, 0, 0.99, -0.142, 0.024, 0, -0.144, -0.973, 0.182, - 0, 0.006, -0.043, 0.002, 1, - ], - }, - 'middle-finger-phalanx-distal': { - radius: 0.007629410829395056, - transform: [ - 0.174, -0.155, -0.972, 0, 0.593, 0.805, -0.022, 0, 0.786, -0.573, 0.232, - 0, 0.01, -0.017, -0.003, 1, - ], - }, - 'middle-finger-tip': { - radius: 0.006629410665482283, - transform: [ - 0.174, -0.155, -0.972, 0, 0.593, 0.805, -0.022, 0, 0.786, -0.573, 0.232, - 0, -0.009, -0.001, -0.009, 1, - ], - }, - 'ring-finger-metacarpal': { - radius: 0.019088275730609894, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.031, 0.031, 0.017, 1], - }, - 'ring-finger-phalanx-proximal': { - radius: 0.00992213748395443, - transform: [ - -0.087, -0.215, -0.973, 0, -0.186, -0.956, 0.228, 0, -0.979, 0.201, 0.043, - 0, -0.03, -0.023, 0.019, 1, - ], - }, - 'ring-finger-phalanx-intermediate': { - radius: 0.007611672393977642, - transform: [ - -0.074, -0.305, -0.949, 0, 0.991, -0.126, -0.037, 0, -0.108, -0.944, - 0.312, 0, 0.008, -0.031, 0.017, 1, - ], - }, - 'ring-finger-phalanx-distal': { - radius: 0.007231088820844889, - transform: [ - -0.001, -0.333, -0.943, 0, 0.583, 0.766, -0.271, 0, 0.812, -0.55, 0.193, - 0, 0.011, -0.006, 0.009, 1, - ], - }, - 'ring-finger-tip': { - radius: 0.0062310886569321156, - transform: [ - -0.001, -0.333, -0.943, 0, 0.583, 0.766, -0.271, 0, 0.812, -0.55, 0.193, - 0, -0.008, 0.009, 0.004, 1, - ], - }, - 'pinky-finger-metacarpal': { - radius: 0.01808827556669712, - transform: [ - -0.396, -0.279, -0.875, 0, -0.914, 0.023, 0.406, 0, -0.094, 0.96, -0.264, - 0, -0.028, 0.032, 0.025, 1, - ], - }, - 'pinky-finger-phalanx-proximal': { - radius: 0.008483353070914745, - transform: [ - -0.266, -0.273, -0.925, 0, -0.203, -0.922, 0.33, 0, -0.942, 0.276, 0.19, - 0, -0.023, -0.012, 0.037, 1, - ], - }, - 'pinky-finger-phalanx-intermediate': { - radius: 0.0067641944624483585, - transform: [ - -0.167, -0.416, -0.894, 0, 0.961, -0.272, -0.053, 0, -0.221, -0.868, - 0.445, 0, 0.006, -0.021, 0.031, 1, - ], - }, - 'pinky-finger-phalanx-distal': { - radius: 0.0064259846694767475, - transform: [ - -0.147, -0.491, -0.859, 0, 0.681, 0.579, -0.448, 0, 0.717, -0.651, 0.249, - 0, 0.01, -0.003, 0.022, 1, - ], - }, - 'pinky-finger-tip': { - radius: 0.005425984505563974, - transform: [ - -0.147, -0.491, -0.859, 0, 0.681, 0.579, -0.448, 0, 0.717, -0.651, 0.249, - 0, -0.005, 0.012, 0.016, 1, - ], - }, -}; - -export const relaxedHandPose = { - wrist: { - radius: 0.021460847929120064, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.037, 0.066, 0.001, 1], - }, - 'thumb-metacarpal': { - radius: 0.019382517784833908, - transform: [ - 0.067, -0.398, 0.915, 0, 0.805, 0.563, 0.186, 0, -0.589, 0.724, 0.358, 0, - -0.015, 0.028, -0.024, 1, - ], - }, - 'thumb-phalanx-proximal': { - radius: 0.01228295173496008, - transform: [ - 0.087, -0.137, 0.987, 0, 0.86, 0.511, -0.005, 0, -0.504, 0.849, 0.162, 0, - 0.004, 0.005, -0.035, 1, - ], - }, - 'thumb-phalanx-distal': { - radius: 0.009768804535269737, - transform: [ - -0.054, -0.22, 0.974, 0, 0.796, 0.58, 0.175, 0, -0.604, 0.784, 0.144, 0, - 0.021, -0.024, -0.041, 1, - ], - }, - 'thumb-tip': { - radius: 0.008768804371356964, - transform: [ - -0.054, -0.22, 0.974, 0, 0.796, 0.58, 0.175, 0, -0.604, 0.784, 0.144, 0, - 0.036, -0.043, -0.045, 1, - ], - }, - 'index-finger-metacarpal': { - radius: 0.021228281781077385, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.025, 0.028, -0.016, 1], - }, - 'index-finger-phalanx-proximal': { - radius: 0.010295259766280651, - transform: [ - 0.064, 0.038, -0.997, 0, -0.965, -0.253, -0.072, 0, -0.255, 0.967, 0.02, - 0, -0.03, -0.03, -0.022, 1, - ], - }, - 'index-finger-phalanx-intermediate': { - radius: 0.00853810179978609, - transform: [ - 0.019, 0.012, -1, 0, -0.679, -0.734, -0.021, 0, -0.734, 0.68, -0.006, 0, - -0.02, -0.067, -0.023, 1, - ], - }, - 'index-finger-phalanx-distal': { - radius: 0.007636196445673704, - transform: [ - 0.037, -0.045, -0.998, 0, -0.546, -0.838, 0.018, 0, -0.837, 0.544, -0.055, - 0, -0.002, -0.084, -0.023, 1, - ], - }, - 'index-finger-tip': { - radius: 0.006636196281760931, - transform: [ - 0.037, -0.045, -0.998, 0, -0.546, -0.838, 0.018, 0, -0.837, 0.544, -0.055, - 0, 0.016, -0.097, -0.021, 1, - ], - }, - 'middle-finger-metacarpal': { - radius: 0.021231964230537415, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.028, 0.03, 0, 1], - }, - 'middle-finger-phalanx-proximal': { - radius: 0.01117393933236599, - transform: [ - -0.012, -0.101, -0.995, 0, -0.982, -0.188, 0.031, 0, -0.19, 0.977, -0.097, - 0, -0.035, -0.03, 0, 1, - ], - }, - 'middle-finger-phalanx-intermediate': { - radius: 0.008030958473682404, - transform: [ - -0.034, -0.114, -0.993, 0, -0.791, -0.604, 0.096, 0, -0.611, 0.788, -0.07, - 0, -0.026, -0.072, 0.004, 1, - ], - }, - 'middle-finger-phalanx-distal': { - radius: 0.007629410829395056, - transform: [ - -0.106, -0.17, -0.98, 0, -0.753, -0.63, 0.191, 0, -0.65, 0.758, -0.062, 0, - -0.01, -0.094, 0.006, 1, - ], - }, - 'middle-finger-tip': { - radius: 0.006629410665482283, - transform: [ - -0.106, -0.17, -0.98, 0, -0.753, -0.63, 0.191, 0, -0.65, 0.758, -0.062, 0, - 0.006, -0.113, 0.008, 1, - ], - }, - 'ring-finger-metacarpal': { - radius: 0.019088275730609894, - transform: [0, 0, -1, 0, -1, 0, 0, 0, 0, 1, 0, 0, -0.031, 0.031, 0.016, 1], - }, - 'ring-finger-phalanx-proximal': { - radius: 0.00992213748395443, - transform: [ - -0.093, -0.232, -0.968, 0, -0.978, -0.163, 0.133, 0, -0.189, 0.959, - -0.211, 0, -0.031, -0.023, 0.019, 1, - ], - }, - 'ring-finger-phalanx-intermediate': { - radius: 0.007611672393977642, - transform: [ - -0.165, -0.251, -0.954, 0, -0.82, -0.502, 0.274, 0, -0.548, 0.828, -0.123, - 0, -0.023, -0.06, 0.027, 1, - ], - }, - 'ring-finger-phalanx-distal': { - radius: 0.007231088820844889, - transform: [ - -0.215, -0.214, -0.953, 0, -0.708, -0.637, 0.303, 0, -0.672, 0.74, -0.015, - 0, -0.009, -0.082, 0.03, 1, - ], - }, - 'ring-finger-tip': { - radius: 0.0062310886569321156, - transform: [ - -0.215, -0.214, -0.953, 0, -0.708, -0.637, 0.303, 0, -0.672, 0.74, -0.015, - 0, 0.007, -0.101, 0.031, 1, - ], - }, - 'pinky-finger-metacarpal': { - radius: 0.01808827556669712, - transform: [ - -0.396, -0.279, -0.875, 0, -0.914, 0.023, 0.406, 0, -0.094, 0.96, -0.264, - 0, -0.028, 0.032, 0.024, 1, - ], - }, - 'pinky-finger-phalanx-proximal': { - radius: 0.008483353070914745, - transform: [ - -0.205, -0.431, -0.879, 0, -0.949, -0.131, 0.286, 0, -0.238, 0.893, - -0.383, 0, -0.023, -0.012, 0.036, 1, - ], - }, - 'pinky-finger-phalanx-intermediate': { - radius: 0.0067641944624483585, - transform: [ - -0.269, -0.517, -0.813, 0, -0.812, -0.332, 0.48, 0, -0.518, 0.789, -0.331, - 0, -0.016, -0.04, 0.048, 1, - ], - }, - 'pinky-finger-phalanx-distal': { - radius: 0.0064259846694767475, - transform: [ - -0.321, -0.438, -0.84, 0, -0.759, -0.411, 0.505, 0, -0.566, 0.799, -0.201, - 0, -0.006, -0.056, 0.055, 1, - ], - }, - 'pinky-finger-tip': { - radius: 0.005425984505563974, - transform: [ - -0.321, -0.438, -0.84, 0, -0.759, -0.411, 0.505, 0, -0.566, 0.799, -0.201, - 0, 0.006, -0.074, 0.06, 1, - ], - }, -}; - -export const HAND_POSES = { - pinch: pinchHandPose, - relaxed: relaxedHandPose, - point: pointHandPose, -}; diff --git a/webxr-emulator/api/index.js b/webxr-emulator/api/index.js deleted file mode 100644 index a4981b8..0000000 --- a/webxr-emulator/api/index.js +++ /dev/null @@ -1,30 +0,0 @@ -import { XRAnchor, XRAnchorSet } from './XRAnchor'; -import { XRMesh, XRMeshSet } from './XRMesh'; -import { XRPlane, XRPlaneOrientation, XRPlaneSet } from './XRPlane'; - -import { XRHand } from './XRHand'; -import XRHitTestResult from './XRHitTestResult'; -import XRHitTestSource from './XRHitTestSource'; -import { XRJointPose } from './XRJointPose'; -import { XRJointSpace } from './XRJointSpace'; -import XRRay from './XRRay'; -import XRTransientInputHitTestResult from './XRTransientInputHitTestResult'; -import XRTransientInputHitTestSource from './XRTransientInputHitTestSource'; - -export default { - XRHitTestResult, - XRHitTestSource, - XRTransientInputHitTestResult, - XRTransientInputHitTestSource, - XRRay, - XRPlane, - XRPlaneSet, - XRAnchor, - XRAnchorSet, - XRPlaneOrientation, - XRHand, - XRJointPose, - XRJointSpace, - XRMesh, - XRMeshSet, -};