diff --git a/Examples/Geometry/VR/index.js b/Examples/Geometry/VR/index.js index 332bd11401a..71164aa23be 100644 --- a/Examples/Geometry/VR/index.js +++ b/Examples/Geometry/VR/index.js @@ -36,7 +36,7 @@ const renderWindow = fullScreenRenderer.getRenderWindow(); // this // ---------------------------------------------------------------------------- -const coneSource = vtkConeSource.newInstance({ height: 100.0, radius: 50 }); +const coneSource = vtkConeSource.newInstance({ height: 1.0, radius: 0.5 }); const filter = vtkCalculator.newInstance(); filter.setInputConnection(coneSource.getOutputPort()); @@ -66,7 +66,7 @@ mapper.setInputConnection(filter.getOutputPort()); const actor = vtkActor.newInstance(); actor.setMapper(mapper); -actor.setPosition(0.0, 0.0, -20.0); +actor.setPosition(0.0, 0.0, -1.0); renderer.addActor(actor); renderer.resetCamera(); diff --git a/Sources/Interaction/Style/InteractorStyleTrackballCamera/index.js b/Sources/Interaction/Style/InteractorStyleTrackballCamera/index.js index c07be922153..b73c78380f5 100644 --- a/Sources/Interaction/Style/InteractorStyleTrackballCamera/index.js +++ b/Sources/Interaction/Style/InteractorStyleTrackballCamera/index.js @@ -3,6 +3,7 @@ import vtkInteractorStyle from 'vtk.js/Sources/Rendering/Core/InteractorStyle'; import vtkInteractorStyleConstants from 'vtk.js/Sources/Rendering/Core/InteractorStyle/Constants'; import * as vtkMath from 'vtk.js/Sources/Common/Core/Math'; import { + Axis, Device, Input, } from 'vtk.js/Sources/Rendering/Core/RenderWindowInteractor/Constants'; @@ -58,7 +59,7 @@ function vtkInteractorStyleTrackballCamera(publicAPI, model) { ed && ed.pressed && ed.device === Device.RightController && - ed.input === Input.TrackPad + ed.input === Input.Trigger // || ed.input === Input.TrackPad) ) { publicAPI.startCameraPose(); return; @@ -67,37 +68,64 @@ function vtkInteractorStyleTrackballCamera(publicAPI, model) { ed && !ed.pressed && ed.device === Device.RightController && - ed.input === Input.TrackPad && + ed.input === Input.Trigger && // || ed.input === Input.TrackPad) && model.state === States.IS_CAMERA_POSE ) { publicAPI.endCameraPose(); // return; } + if ( + ed && + ed.device === Device.RightController && + ed.input && + (ed.input === Axis.ThumbstickX || Input.TrackPad) + ) { + publicAPI.updateCameraOrientation(ed); + } + if (ed && ed.input && (ed.input === Axis.ThumbstickX || Input.TrackPad)) { + publicAPI.updateCameraOrientation(ed); + } }; publicAPI.handleMove3D = (ed) => { switch (model.state) { case States.IS_CAMERA_POSE: - publicAPI.updateCameraPose(ed); + publicAPI.updateCameraPosition(ed); break; default: } }; - publicAPI.updateCameraPose = (ed) => { + publicAPI.updateCameraOrientation = (ed) => { + // rotate the world in the direction + // of the controller + const camera = ed.pokedRenderer.getActiveCamera(); + let worldMatrix = new Float64Array(16); + camera.getWorldToPhysicalMatrix(worldMatrix); + + const angle = ed.device === Device.LeftController ? -22.5 : 22.5; + camera.applyPhysicalYaw(angle); + }; + + publicAPI.updateCameraPosition = (ed) => { // move the world in the direction of the // controller const camera = ed.pokedRenderer.getActiveCamera(); const oldTrans = camera.getPhysicalTranslation(); // look at the y axis to determine how fast / what direction to move - const speed = ed.gamepad.axes[1]; + const speed = 0.5; // ed.gamepad.axes[1]; // 0.05 meters / frame movement const pscale = speed * 0.05 * camera.getPhysicalScale(); // convert orientation to world coordinate direction - const dir = camera.physicalOrientationToWorldDirection(ed.orientation); + const dir = camera.physicalOrientationToWorldDirection([ + ed.orientation.x, + ed.orientation.y, + ed.orientation.z, + ed.orientation.w, + ]); camera.setPhysicalTranslation( oldTrans[0] + dir[0] * pscale, diff --git a/Sources/Rendering/Core/Camera/index.js b/Sources/Rendering/Core/Camera/index.js index 0fa32b74d3d..d8b763b7b7b 100644 --- a/Sources/Rendering/Core/Camera/index.js +++ b/Sources/Rendering/Core/Camera/index.js @@ -399,9 +399,43 @@ function vtkCamera(publicAPI, model) { mat4.translate(result, result, model.physicalTranslation); }; + publicAPI.applyPhysicalYaw = (angle) => { + model.physicalYawAngle += angle; + if (model.physicalYawAngle > 360) { + model.physicalYawAngle -= 360; + } else if (model.physicalYawAngle < 0) { + model.physicalYawAngle += 360; + } + }; + publicAPI.computeViewParametersFromViewMatrix = (vmat) => { // invert to get view to world - mat4.invert(tmpMatrix, vmat); + let viewToWorldPos = new Float64Array(4); + let v2wMatrix = new Float64Array(16); + mat4.invert(v2wMatrix, vmat); + + viewToWorldPos[0] = v2wMatrix[12]; + viewToWorldPos[1] = v2wMatrix[13]; + viewToWorldPos[2] = v2wMatrix[14]; + viewToWorldPos[3] = 1; + + mat4.copy(tmpMatrix, v2wMatrix); + + let rotate = mat4.identity(new Float64Array(16)); + let v2wTranslate = mat4.identity(new Float64Array(16)); + let v2wTranslateReverse = mat4.identity(new Float64Array(16)); + + mat4.translate(v2wTranslate, v2wTranslate, [ + -viewToWorldPos[0], + -viewToWorldPos[1], + -viewToWorldPos[2], + ]); + mat4.rotateY(rotate, rotate, (model.physicalYawAngle / 180) * 3.14); + mat4.translate(v2wTranslateReverse, v2wTranslateReverse, viewToWorldPos); + + mat4.mul(tmpMatrix, v2wTranslate, v2wMatrix); + mat4.mul(tmpMatrix, tmpMatrix, rotate); + mat4.mul(tmpMatrix, v2wTranslateReverse, tmpMatrix); // note with glmatrix operations happen in // the reverse order @@ -443,6 +477,27 @@ function vtkCamera(publicAPI, model) { // world -> view mat4.multiply(tmpMatrix, mat, tmpMatrix); + console.log('orig'); + console.log(tmpMatrix); + let rot = mat4.identity(new Float64Array(16)); + let v = new Float64Array(3); + v[0] = tmpMatrix[12]; + v[1] = tmpMatrix[13]; + v[2] = tmpMatrix[14]; + //mat4.rotateY(rot, rot, model.physicalYawAngle / 180 * 3.14); + // mat4.transpose(rot, rot); + //mat4.translate(tmpMatrix, tmpMatrix, [-v[0], -v[1], -v[2]]); + mat4.mul(tmpMatrix, tmpMatrix, rot); + tmpMatrix[12] = v[0]; + tmpMatrix[13] = v[1]; + tmpMatrix[14] = v[2]; + //mat4.mul(tmpMatrix, rot, tmpMatrix); + //mat4.translate(tmpMatrix, tmpMatrix, v); + + console.log(tmpMatrix); + let tmp = tmpMatrix[12]; + //tmpMatrix[12] = tmpMatrix[14]; + //tmpMatrix[14] = tmp; publicAPI.computeViewParametersFromViewMatrix(tmpMatrix); }; @@ -721,6 +776,8 @@ export const DEFAULT_VALUES = { physicalScale: 1.0, physicalViewUp: [0, 1, 0], physicalViewNorth: [0, 0, -1], + + physicalYawAngle: 0, }; // ---------------------------------------------------------------------------- diff --git a/Sources/Rendering/Core/RenderWindowInteractor/Constants.js b/Sources/Rendering/Core/RenderWindowInteractor/Constants.js index 79a4de2fd8a..546bdc42522 100644 --- a/Sources/Rendering/Core/RenderWindowInteractor/Constants.js +++ b/Sources/Rendering/Core/RenderWindowInteractor/Constants.js @@ -9,10 +9,22 @@ export const Input = { Trigger: 1, TrackPad: 2, Grip: 3, - ApplicationMenu: 4, + Thumbstick: 4, + A: 5, + B: 6, + ApplicationMenu: 7, // Not exposed in WebXR API +}; + +export const Axis = { + Unknown: 0, + TouchpadX: 1, + TouchpadY: 2, + ThumbstickX: 3, + ThumbstickY: 4, }; export default { Device, Input, + Axis, }; diff --git a/Sources/Rendering/Core/RenderWindowInteractor/index.js b/Sources/Rendering/Core/RenderWindowInteractor/index.js index b5ec257e2f8..8907750a3ce 100644 --- a/Sources/Rendering/Core/RenderWindowInteractor/index.js +++ b/Sources/Rendering/Core/RenderWindowInteractor/index.js @@ -3,7 +3,7 @@ import * as vtkMath from 'vtk.js/Sources/Common/Core/Math'; import Constants from 'vtk.js/Sources/Rendering/Core/RenderWindowInteractor/Constants'; -const { Device, Input } = Constants; +const { Axis, Device, Input } = Constants; const { vtkWarningMacro, vtkErrorMacro, normalizeWheel, vtkOnceErrorMacro } = macro; @@ -12,12 +12,17 @@ const { vtkWarningMacro, vtkErrorMacro, normalizeWheel, vtkOnceErrorMacro } = // ---------------------------------------------------------------------------- const deviceInputMap = { - 'OpenVR Gamepad': [ - Input.TrackPad, - Input.Trigger, - Input.Grip, - Input.ApplicationMenu, - ], + 'xr-standard': { + button: [ + Input.Trigger, + Input.Grip, + Input.TrackPad, + Input.Thumbstick, + Input.A, + Input.B, + ], + axis: [Axis.TouchpadX, Axis.TouchpadY, Axis.ThumbstickX, Axis.ThumbstickY], + }, }; const handledEvents = [ @@ -415,55 +420,107 @@ function vtkRenderWindowInteractor(publicAPI, model) { } }; - publicAPI.updateGamepads = (displayId) => { - const gamepads = navigator.getGamepads(); - + publicAPI.updateXRGamepads = (xrSession, xrFrame, xrRefSpace) => { + // Fire binary events when axis magnitude crosses threshold + const axisThreshold = 0.9; // watch for when buttons change state and fire events - for (let i = 0; i < gamepads.length; ++i) { - const gp = gamepads[i]; - if (gp && gp.displayId === displayId) { - if (!(gp.index in model.lastGamepadValues)) { - model.lastGamepadValues[gp.index] = { buttons: {} }; + xrSession.inputSources.forEach((inputSource) => { + const gp = inputSource.gamepad; + if (gp === null) return; + + const pose = xrFrame.getPose(inputSource.gripSpace, xrRefSpace); + const hand = inputSource.handedness; + + // Init + if (!(gp.index in model.lastGamepadValues)) { + model.lastGamepadValues[gp.index] = { + left: { + buttons: {}, + axes: {}, + }, + right: { + buttons: {}, + axes: {}, + }, + }; + } + + // Query buttons + for (let b = 0; b < gp.buttons.length; ++b) { + // Init + if (!(b in model.lastGamepadValues[gp.index][hand].buttons)) { + model.lastGamepadValues[gp.index][hand].buttons[b] = false; + } + + // State change + if ( + model.lastGamepadValues[gp.index][hand].buttons[b] !== + gp.buttons[b].pressed + ) { + publicAPI.button3DEvent({ + gamepad: gp, + position: pose.transform.position, + orientation: pose.transform.orientation, + pressed: gp.buttons[b].pressed, + device: + hand === 'left' ? Device.LeftController : Device.RightController, + input: + deviceInputMap[gp.mapping] && + deviceInputMap[gp.mapping]['button'][b] + ? deviceInputMap[gp.mapping]['button'][b] + : Input.Trigger, + }); + model.lastGamepadValues[gp.index][hand].buttons[b] = + gp.buttons[b].pressed; } - for (let b = 0; b < gp.buttons.length; ++b) { - if (!(b in model.lastGamepadValues[gp.index].buttons)) { - model.lastGamepadValues[gp.index].buttons[b] = false; - } - if ( - model.lastGamepadValues[gp.index].buttons[b] !== - gp.buttons[b].pressed - ) { - publicAPI.button3DEvent({ - gamepad: gp, - position: gp.pose.position, - orientation: gp.pose.orientation, - pressed: gp.buttons[b].pressed, - device: - gp.hand === 'left' - ? Device.LeftController - : Device.RightController, - input: - deviceInputMap[gp.id] && deviceInputMap[gp.id][b] - ? deviceInputMap[gp.id][b] - : Input.Trigger, - }); - model.lastGamepadValues[gp.index].buttons[b] = - gp.buttons[b].pressed; - } - if (model.lastGamepadValues[gp.index].buttons[b]) { - publicAPI.move3DEvent({ - gamepad: gp, - position: gp.pose.position, - orientation: gp.pose.orientation, - device: - gp.hand === 'left' - ? Device.LeftController - : Device.RightController, - }); - } + + // State + if (gp.buttons[b].pressed) { + publicAPI.move3DEvent({ + gamepad: gp, + position: pose.transform.position, + orientation: pose.transform.orientation, + device: + hand === 'left' ? Device.LeftController : Device.RightController, + input: + deviceInputMap[gp.mapping] && + deviceInputMap[gp.mapping]['button'][b] + ? deviceInputMap[gp.mapping]['button'][b] + : Input.Unknown, + }); } } - } + + for (let a = 0; a < gp.axes.length; ++a) { + // Init + if (!(a in model.lastGamepadValues[gp.index][hand].axes)) { + model.lastGamepadValues[gp.index][hand].axes[a] = 0.0; + } + + // State change + if ( + gp.axes[a] > axisThreshold && + model.lastGamepadValues[gp.index][hand].axes[a] < axisThreshold + ) { + } + + // State + // TODO debounce + if (gp.axes[a] > axisThreshold) { + //model.rotate3DEvent({ + // gamepad: gp, + // position: pose.transform.position, + // orientation: pose.transform.orientation, + // device: + // hand === 'left' ? Device.LeftController : Device.RightController, + // input: deviceInputMap[gp.mapping] && deviceInputMap[gp.mapping]['axis'][a] + // ? deviceInputMap[gp.mapping]['axis'][a] + // : Axis.Unknown, + // value: gp.axes[a], + //}); + } + } + }); }; publicAPI.handleMouseMove = (event) => { diff --git a/Sources/Rendering/OpenGL/RenderWindow/index.js b/Sources/Rendering/OpenGL/RenderWindow/index.js index ed6d87ed230..cfc8ad92934 100644 --- a/Sources/Rendering/OpenGL/RenderWindow/index.js +++ b/Sources/Rendering/OpenGL/RenderWindow/index.js @@ -354,6 +354,10 @@ function vtkOpenGLRenderWindow(publicAPI, model) { publicAPI.xrRender = async (t, frame) => { const xrSession = frame.session; + model.renderable + .getInteractor() + .updateXRGamepads(xrSession, frame, model.xrReferenceSpace); + model.xrSceneFrame = model.xrSession.requestAnimationFrame( publicAPI.xrRender ); @@ -384,11 +388,11 @@ function vtkOpenGLRenderWindow(publicAPI, model) { return; } - ren - .getActiveCamera() - .computeViewParametersFromPhysicalMatrix( - view.transform.inverse.matrix - ); + ren.getActiveCamera().computeViewParametersFromViewMatrix( + // FIXME use pose for each eye + //view.transform.inverse.matrix + xrPose.transform.inverse.matrix + ); ren.getActiveCamera().setProjectionMatrix(view.projectionMatrix); publicAPI.traverseAllPasses();