diff --git a/Examples/OpenVRController/OpenVRControllerExample.cpp b/Examples/OpenVRController/OpenVRControllerExample.cpp index fc23cdab5aec81dec7f5497d9782ee2d68079e69..8180016d1ef48496e07b3448798b6acafedcc5c8 100644 --- a/Examples/OpenVRController/OpenVRControllerExample.cpp +++ b/Examples/OpenVRController/OpenVRControllerExample.cpp @@ -137,7 +137,7 @@ main() imstkNew<SimulationManager> driver; driver->addModule(viewer); driver->addModule(sceneManager); - driver->setDesiredDt(0.01); + driver->setDesiredDt(0.01); // Spend less time updating & more time rendering // Add a VR controller for the scalpel handle imstkNew<SceneObjectController> controller1(scalpelHandle, viewer->getVRDeviceClient(OPENVR_RIGHT_CONTROLLER)); diff --git a/Source/SimulationManager/imstkSimulationManager.cpp b/Source/SimulationManager/imstkSimulationManager.cpp index 8459272e0a288e9b4af4ef251aaf7e809a1b1a78..4a168e1bef405e24fa6a923492594772cf2af2a3 100644 --- a/Source/SimulationManager/imstkSimulationManager.cpp +++ b/Source/SimulationManager/imstkSimulationManager.cpp @@ -148,11 +148,6 @@ SimulationManager::start() // Actual game loop { - for (auto viewer : m_viewers) - { - viewer->processEvents(); - } - for (auto syncModule : m_syncModules) { syncModule->setDt(m_dt); @@ -164,6 +159,11 @@ SimulationManager::start() adaptiveModule->setDt(m_dt); for (int currStep = 0; currStep < m_numSteps; currStep++) { + // Process system & input events (ie: VR, hmd pose, mkd, OS msgs updates) + for (auto viewer : m_viewers) + { + viewer->processEvents(); + } adaptiveModule->update(); } } diff --git a/Source/ViewerVTK/imstkVTKInteractorStyleVR.cpp b/Source/ViewerVTK/imstkVTKInteractorStyleVR.cpp index 779b11e670ec03b7abf282ddc8fc840a0dae50fc..74a2d030bfee3c06ed318b88b894f317eff7ed2a 100644 --- a/Source/ViewerVTK/imstkVTKInteractorStyleVR.cpp +++ b/Source/ViewerVTK/imstkVTKInteractorStyleVR.cpp @@ -26,7 +26,8 @@ #include <vtkMath.h> #include <vtkEventData.h> #include <vtkObjectFactory.h> -#include <vtkOpenVRRenderWindowInteractor.h> +#include "imstkVtkOpenVRRenderWindowInteractor2.h" +//#include <vtkOpenVRRenderWindowInteractor.h> vtkStandardNewMacro(vtkInteractorStyleVR); @@ -72,8 +73,8 @@ vtkInteractorStyleVR::OnButtonPress(vtkEventData* data, int buttonId) void vtkInteractorStyleVR::addMovementActions() { - vtkOpenVRRenderWindowInteractor* iren = - vtkOpenVRRenderWindowInteractor::SafeDownCast(GetInteractor()); + vtkOpenVRRenderWindowInteractor2* iren = + vtkOpenVRRenderWindowInteractor2::SafeDownCast(GetInteractor()); CHECK(iren->GetInitialized()) << "Cannot addMovementActions to style until " "interactor has been initialized"; iren->AddAction("/actions/vtk/in/LeftGripMovement", true, @@ -95,8 +96,8 @@ vtkInteractorStyleVR::addMovementActions() void vtkInteractorStyleVR::addButtonActions() { - vtkOpenVRRenderWindowInteractor* iren = - vtkOpenVRRenderWindowInteractor::SafeDownCast(GetInteractor()); + vtkOpenVRRenderWindowInteractor2* iren = + vtkOpenVRRenderWindowInteractor2::SafeDownCast(GetInteractor()); CHECK(iren->GetInitialized()) << "Cannot addButtonActions to style until " "interactor has been initialized"; diff --git a/Source/ViewerVTK/imstkVTKOpenVRViewer.cpp b/Source/ViewerVTK/imstkVTKOpenVRViewer.cpp index 3f8786f079b91a16fc72a5f050f0a51495580d51..89a952cd07d0d626fcdd29c73f26d650363e313c 100644 --- a/Source/ViewerVTK/imstkVTKOpenVRViewer.cpp +++ b/Source/ViewerVTK/imstkVTKOpenVRViewer.cpp @@ -28,7 +28,9 @@ #include "imstkVTKInteractorStyleVR.h" #include "imstkVTKRenderer.h" -#include <vtkOpenVRRenderWindowInteractor.h> +#include "imstkVtkOpenVRRenderWindowInteractor2.h" + +//#include <vtkOpenVRRenderWindowInteractor.h> #include <vtkMatrix4x4.h> #include <vtkOpenVRRenderer.h> #include <vtkOpenVRRenderWindow.h> @@ -43,7 +45,7 @@ VTKOpenVRViewer::VTKOpenVRViewer(std::string name) : AbstractVTKViewer(name) m_vtkInteractorStyle = vrInteractorStyle; // Create the interactor - auto iren = vtkSmartPointer<vtkOpenVRRenderWindowInteractor>::New(); + auto iren = vtkSmartPointer<vtkOpenVRRenderWindowInteractor2>::New(); iren->SetInteractorStyle(m_vtkInteractorStyle); // Create the RenderWindow @@ -143,6 +145,21 @@ VTKOpenVRViewer::setRenderingMode(const Renderer::Mode mode) this->getActiveRenderer()->setMode(mode, true); } +void +VTKOpenVRViewer::processEvents() +{ + // Custom call to only process input events, do not perform a render + auto iren = vtkOpenVRRenderWindowInteractor2::SafeDownCast(m_vtkRenderWindow->GetInteractor()); + auto ren = std::dynamic_pointer_cast<imstk::VTKRenderer>(getActiveRenderer()); + iren->DoOneEvent(vtkOpenVRRenderWindow::SafeDownCast(m_vtkRenderWindow), ren->getVtkRenderer(), false); + + // Update all controls + for (auto control : m_controls) + { + control->update(m_dt); + } +} + bool VTKOpenVRViewer::initModule() { @@ -153,7 +170,7 @@ VTKOpenVRViewer::initModule() // VR interactor doesn't support timers, here we throw timer event every update // another option would be to conform VTKs VR interactor - auto iren = vtkOpenVRRenderWindowInteractor::SafeDownCast(m_vtkRenderWindow->GetInteractor()); + auto iren = vtkOpenVRRenderWindowInteractor2::SafeDownCast(m_vtkRenderWindow->GetInteractor()); //iren->Start(); // Cannot use if (iren->HasObserver(vtkCommand::StartEvent)) { @@ -191,7 +208,7 @@ VTKOpenVRViewer::initModule() void VTKOpenVRViewer::updateModule() { - std::shared_ptr<imstk::VTKRenderer> ren = std::dynamic_pointer_cast<imstk::VTKRenderer>(getActiveRenderer()); + auto ren = std::dynamic_pointer_cast<imstk::VTKRenderer>(getActiveRenderer()); if (ren == nullptr) { return; diff --git a/Source/ViewerVTK/imstkVTKOpenVRViewer.h b/Source/ViewerVTK/imstkVTKOpenVRViewer.h index 023ee9c8114660a71f82274e993bd0e71d55d612..aafe45d48cc8326b98838609a2b32226fe9f4693 100644 --- a/Source/ViewerVTK/imstkVTKOpenVRViewer.h +++ b/Source/ViewerVTK/imstkVTKOpenVRViewer.h @@ -76,6 +76,12 @@ public: /// const std::list<std::shared_ptr<OpenVRDeviceClient>>& getVRDeviceClients() const { return m_vrDeviceClients; } + /// + /// \brief VTKOpenVRViewer overrides to provide a non-rendering + /// event processing loop (to deal with vsync blockage) + /// + void processEvents() override; + protected: bool initModule() override; diff --git a/Source/ViewerVTK/imstkVtkOpenVRRenderWindowInteractor2.cpp b/Source/ViewerVTK/imstkVtkOpenVRRenderWindowInteractor2.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2753a4a1d84f76a0a08b9b6d8e61fc6aa0761f4c --- /dev/null +++ b/Source/ViewerVTK/imstkVtkOpenVRRenderWindowInteractor2.cpp @@ -0,0 +1,907 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkOpenVRRenderWindowInteractor2.cxx + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +#include <cassert> +#include <cmath> +#include <cstdio> +#include <cstdlib> +#include <cstring> + +#include "vtkOpenGLState.h" +#include "vtkOpenVROverlay.h" +#include "vtkOpenVRRenderWindow.h" +#include "imstkVtkOpenVRRenderWindowInteractor2.h" +#include "vtkRendererCollection.h" +#include "vtkVRRenderWindow.h" + +#include "vtkEventData.h" + +#include "vtkActor.h" +#include "vtkCommand.h" +#include "vtkMatrix4x4.h" +#include "vtkNew.h" +#include "vtkObjectFactory.h" +#include "vtkOpenVRCamera.h" +#include "vtkOpenVRInteractorStyle.h" +#include "vtkTextureObject.h" +#include "vtkTransform.h" +#include <vtksys/SystemTools.hxx> + +vtkStandardNewMacro(vtkOpenVRRenderWindowInteractor2); + +void (*vtkOpenVRRenderWindowInteractor2::ClassExitMethod)(void*) = (void (*)(void*)) nullptr; +void* vtkOpenVRRenderWindowInteractor2::ClassExitMethodArg = (void*)nullptr; +void (*vtkOpenVRRenderWindowInteractor2::ClassExitMethodArgDelete)( + void*) = (void (*)(void*)) nullptr; + +//------------------------------------------------------------------------------ +// Construct object so that light follows camera motion. +vtkOpenVRRenderWindowInteractor2::vtkOpenVRRenderWindowInteractor2() +{ + vtkNew<vtkOpenVRInteractorStyle> style; + this->SetInteractorStyle(style); + + for (int i = 0; i < vtkEventDataNumberOfDevices; i++) + { + this->DeviceInputDownCount[i] = 0; + } + this->ActionManifestFileName = "./vtk_openvr_actions.json"; + this->ActionSetName = "/actions/vtk"; +} + +//------------------------------------------------------------------------------ +vtkOpenVRRenderWindowInteractor2::~vtkOpenVRRenderWindowInteractor2() = default; + +void vtkOpenVRRenderWindowInteractor2::SetPhysicalScale(double scale) +{ + vtkVRRenderWindow* win = vtkVRRenderWindow::SafeDownCast(this->RenderWindow); + win->SetPhysicalScale(scale); +} + +double vtkOpenVRRenderWindowInteractor2::GetPhysicalScale() +{ + vtkVRRenderWindow* win = vtkVRRenderWindow::SafeDownCast(this->RenderWindow); + return win->GetPhysicalScale(); +} + +void vtkOpenVRRenderWindowInteractor2::SetPhysicalTranslation( + vtkCamera*, double t1, double t2, double t3) +{ + vtkVRRenderWindow* win = vtkVRRenderWindow::SafeDownCast(this->RenderWindow); + win->SetPhysicalTranslation(t1, t2, t3); +} + +double* vtkOpenVRRenderWindowInteractor2::GetPhysicalTranslation(vtkCamera*) +{ + vtkVRRenderWindow* win = vtkVRRenderWindow::SafeDownCast(this->RenderWindow); + return win->GetPhysicalTranslation(); +} + +void vtkOpenVRRenderWindowInteractor2::ConvertOpenVRPoseToMatrices( + const vr::TrackedDevicePose_t & tdPose, vtkMatrix4x4 * poseMatrixWorld, + vtkMatrix4x4 * poseMatrixPhysical /*=nullptr*/) +{ + if (!poseMatrixWorld && !poseMatrixPhysical) + { + return; + } + + vtkNew<vtkMatrix4x4> poseMatrixPhysicalTemp; + for (int row = 0; row < 3; ++row) + { + for (int col = 0; col < 4; ++col) + { + poseMatrixPhysicalTemp->SetElement(row, col, tdPose.mDeviceToAbsoluteTracking.m[row][col]); + } + } + if (poseMatrixPhysical) + { + poseMatrixPhysical->DeepCopy(poseMatrixPhysicalTemp); + } + + if (poseMatrixWorld) + { + vtkVRRenderWindow* win = vtkVRRenderWindow::SafeDownCast(this->RenderWindow); + vtkNew<vtkMatrix4x4> physicalToWorldMatrix; + win->GetPhysicalToWorldMatrix(physicalToWorldMatrix); + vtkMatrix4x4::Multiply4x4(physicalToWorldMatrix, poseMatrixPhysicalTemp, poseMatrixWorld); + } +} + +void vtkOpenVRRenderWindowInteractor2::ConvertPoseToWorldCoordinates( + const vr::TrackedDevicePose_t & tdPose, + double pos[3], // Output world position + double wxyz[4], // Output world orientation quaternion + double ppos[3], // Output physical position + double wdir[3]) // Output world view direction (-Z) +{ + // Convenience function to use the openvr-independant function + // TODO: remove it + this->ConvertPoseMatrixToWorldCoordinates( + tdPose.mDeviceToAbsoluteTracking.m, pos, wxyz, ppos, wdir); +} + +void vtkOpenVRRenderWindowInteractor2::ConvertPoseMatrixToWorldCoordinates( + const float poseMatrix[3][4], + double pos[3], // Output world position + double wxyz[4], // Output world orientation quaternion + double ppos[3], // Output physical position + double wdir[3]) // Output world view direction (-Z) +{ + // TODO: define it in generic superclass VRRenderWindowInteractor + vtkVRRenderWindow* win = vtkVRRenderWindow::SafeDownCast(this->RenderWindow); + double physicalScale = win->GetPhysicalScale(); + double* trans = win->GetPhysicalTranslation(); + + // Vive to world axes + double* vup = win->GetPhysicalViewUp(); + double* dop = win->GetPhysicalViewDirection(); + double vright[3]; + vtkMath::Cross(dop, vup, vright); + + // extract HMD axes + double hvright[3]; + hvright[0] = poseMatrix[0][0]; + hvright[1] = poseMatrix[1][0]; + hvright[2] = poseMatrix[2][0]; + double hvup[3]; + hvup[0] = poseMatrix[0][1]; + hvup[1] = poseMatrix[1][1]; + hvup[2] = poseMatrix[2][1]; + + // convert position to world coordinates + // get the position and orientation of the button press + for (int i = 0; i < 3; i++) + { + ppos[i] = poseMatrix[i][3]; + } + + pos[0] = ppos[0] * vright[0] + ppos[1] * vup[0] - ppos[2] * dop[0]; + pos[1] = ppos[0] * vright[1] + ppos[1] * vup[1] - ppos[2] * dop[1]; + pos[2] = ppos[0] * vright[2] + ppos[1] * vup[2] - ppos[2] * dop[2]; + // now adjust for scale and translation + for (int i = 0; i < 3; i++) + { + pos[i] = pos[i] * physicalScale - trans[i]; + } + + // convert axes to world coordinates + double fvright[3]; // final vright + fvright[0] = hvright[0] * vright[0] + hvright[1] * vup[0] - hvright[2] * dop[0]; + fvright[1] = hvright[0] * vright[1] + hvright[1] * vup[1] - hvright[2] * dop[1]; + fvright[2] = hvright[0] * vright[2] + hvright[1] * vup[2] - hvright[2] * dop[2]; + double fvup[3]; // final vup + fvup[0] = hvup[0] * vright[0] + hvup[1] * vup[0] - hvup[2] * dop[0]; + fvup[1] = hvup[0] * vright[1] + hvup[1] * vup[1] - hvup[2] * dop[1]; + fvup[2] = hvup[0] * vright[2] + hvup[1] * vup[2] - hvup[2] * dop[2]; + vtkMath::Cross(fvup, fvright, wdir); + + double ortho[3][3]; + for (int i = 0; i < 3; i++) + { + ortho[i][0] = fvright[i]; + ortho[i][1] = fvup[i]; + ortho[i][2] = -wdir[i]; + } + + vtkMath::Matrix3x3ToQuaternion(ortho, wxyz); + + // calc the return value wxyz + double mag = sqrt(wxyz[1] * wxyz[1] + wxyz[2] * wxyz[2] + wxyz[3] * wxyz[3]); + + if (mag != 0.0) + { + wxyz[0] = 2.0 * vtkMath::DegreesFromRadians(atan2(mag, wxyz[0])); + wxyz[1] /= mag; + wxyz[2] /= mag; + wxyz[3] /= mag; + } + else + { + wxyz[0] = 0.0; + wxyz[1] = 0.0; + wxyz[2] = 0.0; + wxyz[3] = 1.0; + } +} + +//--------------------------------------------------------------------------------------------------------------------- +// Purpose: Returns true if the action is active and its state is true +//--------------------------------------------------------------------------------------------------------------------- +bool GetDigitalActionState( + vr::VRActionHandle_t action, vr::VRInputValueHandle_t * pDevicePath = nullptr) +{ + vr::InputDigitalActionData_t actionData; + vr::VRInput()->GetDigitalActionData( + action, &actionData, sizeof(actionData), vr::k_ulInvalidInputValueHandle); + if (pDevicePath) + { + *pDevicePath = vr::k_ulInvalidInputValueHandle; + if (actionData.bActive) + { + vr::InputOriginInfo_t originInfo; + if (vr::VRInputError_None == + vr::VRInput()->GetOriginTrackedDeviceInfo( + actionData.activeOrigin, &originInfo, sizeof(originInfo))) + { + *pDevicePath = originInfo.devicePath; + } + } + } + return actionData.bActive && actionData.bState; +} + +//------------------------------------------------------------------------------ +void vtkOpenVRRenderWindowInteractor2::StartEventLoop() +{ + this->StartedMessageLoop = 1; + this->Done = false; + + vtkOpenVRRenderWindow* renWin = vtkOpenVRRenderWindow::SafeDownCast(this->RenderWindow); + + vtkRenderer* ren = static_cast<vtkRenderer*>(renWin->GetRenderers()->GetItemAsObject(0)); + + while (!this->Done) + { + ProcessEvents(); + } +} + +void vtkOpenVRRenderWindowInteractor2::ProcessEvents() +{ + vtkOpenVRRenderWindow* renWin = vtkOpenVRRenderWindow::SafeDownCast(this->RenderWindow); + + vtkRenderer* ren = static_cast<vtkRenderer*>(renWin->GetRenderers()->GetItemAsObject(0)); + this->DoOneEvent(renWin, ren, true); +} + +void vtkOpenVRRenderWindowInteractor2::DoOneEvent(vtkOpenVRRenderWindow* renWin, vtkRenderer* ren, bool doRender) +{ + if (!renWin || !ren) + { + return; + } + vr::IVRSystem* pHMD = renWin->GetHMD(); + + if (!pHMD) + { + // try rendering to create the HMD connection + renWin->Render(); + return; + } + + vr::VREvent_t event; + vtkOpenVROverlay* ovl = renWin->GetDashboardOverlay(); + bool result = false; + + if (vr::VROverlay() && vr::VROverlay()->IsOverlayVisible(ovl->GetOverlayHandle())) + { + result = + vr::VROverlay()->PollNextOverlayEvent(ovl->GetOverlayHandle(), &event, sizeof(vr::VREvent_t)); + + if (result) + { + int height = ovl->GetOverlayTexture()->GetHeight(); + switch (event.eventType) + { + case vr::VREvent_MouseButtonDown: + { + if (event.data.mouse.button == vr::VRMouseButton_Left) + { + ovl->MouseButtonPress(event.data.mouse.x, height - event.data.mouse.y - 1); + } + } + break; + + case vr::VREvent_MouseButtonUp: + { + if (event.data.mouse.button == vr::VRMouseButton_Left) + { + ovl->MouseButtonRelease(event.data.mouse.x, height - event.data.mouse.y - 1); + } + } + break; + + case vr::VREvent_MouseMove: + { + ovl->MouseMoved(event.data.mouse.x, height - event.data.mouse.y - 1); + } + break; + + case vr::VREvent_OverlayShown: + { + renWin->RenderOverlay(); + } + break; + + case vr::VREvent_Quit: + this->Done = true; + break; + } + } + + // eat up any pending events + while (pHMD->PollNextEvent(&event, sizeof(vr::VREvent_t))) + { + } + } + else + { + result = pHMD->PollNextEvent(&event, sizeof(vr::VREvent_t)); + + // process all pending events + while (result) + { + result = pHMD->PollNextEvent(&event, sizeof(vr::VREvent_t)); + } + + // Process SteamVR action state + // UpdateActionState is called each frame to update the state of the actions themselves. The + // application controls which action sets are active with the provided array of + // VRActiveActionSet_t structs. + vr::VRActiveActionSet_t actionSet = { 0 }; + actionSet.ulActionSet = this->ActionsetVTK; + vr::VRInput()->UpdateActionState(&actionSet, sizeof(actionSet), 1); + + for (int tracker = 0; tracker < vtkOpenVRRenderWindowInteractor2::NumberOfTrackers; tracker++) + { + vr::InputOriginInfo_t originInfo; + vr::EVRInputError evriError = vr::VRInput()->GetOriginTrackedDeviceInfo( + this->Trackers[tracker].Source, &originInfo, sizeof(vr::InputOriginInfo_t)); + if (evriError != vr::VRInputError_None) + { + // this can happen when a tracker isn't online + continue; + } + + vr::EVRCompositorError evrcError = vr::VRCompositor()->GetLastPoseForTrackedDeviceIndex( + originInfo.trackedDeviceIndex, &this->Trackers[tracker].LastPose, nullptr); + if (evrcError != vr::VRCompositorError_None) + { + vtkErrorMacro("Error in GetLastPoseForTrackedDeviceIndex: " << evrcError); + continue; + } + + if (this->Trackers[tracker].LastPose.bPoseIsValid) + { + double pos[3] = { 0.0 }; + double ppos[3] = { 0.0 }; + double wxyz[4] = { 0.0 }; + double wdir[3] = { 0.0 }; + this->ConvertPoseToWorldCoordinates( + this->Trackers[tracker].LastPose, pos, wxyz, ppos, wdir); + vtkNew<vtkEventDataDevice3D> ed; + ed->SetWorldPosition(pos); + ed->SetWorldOrientation(wxyz); + ed->SetWorldDirection(wdir); + + switch (tracker) + { + case LeftHand: + ed->SetDevice(vtkEventDataDevice::LeftController); + break; + case RightHand: + ed->SetDevice(vtkEventDataDevice::RightController); + break; + case Head: + ed->SetDevice(vtkEventDataDevice::HeadMountedDisplay); + break; + } + ed->SetType(vtkCommand::Move3DEvent); + + // would like to remove the three following lines, they are there mostly to support + // multitouch and event handling where the handler doesn;t use the data in the event + // the first doesn't seem to be a common use pattern for VR, the second isn't used + // to my knowledge + this->SetPointerIndex(static_cast<int>(ed->GetDevice())); + this->SetPhysicalEventPosition(ppos[0], ppos[1], ppos[2], this->PointerIndex); + this->SetWorldEventPosition(pos[0], pos[1], pos[2], this->PointerIndex); + this->SetWorldEventOrientation(wxyz[0], wxyz[1], wxyz[2], wxyz[3], this->PointerIndex); + + if (this->Enabled) + { + this->InvokeEvent(vtkCommand::Move3DEvent, ed); + } + } + } + + // handle other actions + for (auto& it : this->ActionMap) + { + vr::VRActionHandle_t& actionHandle = it.second.ActionHandle; + vtkEventDataDevice3D* edp = nullptr; + vr::VRInputValueHandle_t activeOrigin = 0; + + if (it.second.IsAnalog) + { + vr::InputAnalogActionData_t analogData; + if (vr::VRInput()->GetAnalogActionData(actionHandle, &analogData, sizeof(analogData), + vr::k_ulInvalidInputValueHandle) == vr::VRInputError_None && + analogData.bActive) + { + // send a movement event + edp = vtkEventDataDevice3D::New(); + edp->SetTrackPadPosition(analogData.x, analogData.y); + activeOrigin = analogData.activeOrigin; + } + } + else + { + vr::InputDigitalActionData_t actionData; + auto vrresult = vr::VRInput()->GetDigitalActionData( + actionHandle, &actionData, sizeof(actionData), vr::k_ulInvalidInputValueHandle); + if (vrresult == vr::VRInputError_None && actionData.bActive && actionData.bChanged) + { + edp = vtkEventDataDevice3D::New(); + edp->SetAction( + actionData.bState ? vtkEventDataAction::Press : vtkEventDataAction::Release); + activeOrigin = actionData.activeOrigin; + } + } + + if (edp) + { + double pos[3] = { 0.0 }; + double ppos[3] = { 0.0 }; + double wxyz[4] = { 0.0 }; + double wdir[3] = { 0.0 }; + + vr::InputBindingInfo_t inBindingInfo; + uint32_t returnedBindingInfoCount = 0; + /*vr::EVRInputError abinfo = */ vr::VRInput()->GetActionBindingInfo( + actionHandle, &inBindingInfo, sizeof(inBindingInfo), 1, &returnedBindingInfoCount); + + std::string inputSource = inBindingInfo.rchInputSourceType; + if (inputSource == "trackpad") + { + edp->SetInput(vtkEventDataDeviceInput::TrackPad); + } + else if (inputSource == "joystick") + { + edp->SetInput(vtkEventDataDeviceInput::Joystick); + } + else if (inputSource == "trigger") + { + edp->SetInput(vtkEventDataDeviceInput::Trigger); + } + else if (inputSource == "grip") + { + edp->SetInput(vtkEventDataDeviceInput::Grip); + } + else + { + edp->SetInput(vtkEventDataDeviceInput::Unknown); + } + + vr::InputOriginInfo_t originInfo; + if (vr::VRInputError_None == + vr::VRInput()->GetOriginTrackedDeviceInfo(activeOrigin, &originInfo, sizeof(originInfo))) + { + if (originInfo.devicePath == this->Trackers[LeftHand].Source) + { + edp->SetDevice(vtkEventDataDevice::LeftController); + this->ConvertPoseToWorldCoordinates(this->Trackers[0].LastPose, pos, wxyz, ppos, wdir); + } + if (originInfo.devicePath == this->Trackers[RightHand].Source) + { + edp->SetDevice(vtkEventDataDevice::RightController); + this->ConvertPoseToWorldCoordinates(this->Trackers[1].LastPose, pos, wxyz, ppos, wdir); + } + // edp->SetInput(originInfo.); + } + edp->SetWorldPosition(pos); + edp->SetWorldOrientation(wxyz); + edp->SetWorldDirection(wdir); + edp->SetType(it.second.EventId); + + if (it.second.UseFunction) + { + it.second.Function(edp); + } + else + { + this->InvokeEvent(it.second.EventId, edp); + } + edp->Delete(); + } + } + + if (this->RecognizeGestures) + { + this->RecognizeComplexGesture(nullptr); + } + if (doRender) + { + this->InvokeEvent(vtkCommand::RenderEvent); + auto ostate = renWin->GetState(); + renWin->MakeCurrent(); + ostate->Reset(); + ostate->Push(); + renWin->Render(); + ostate->Pop(); + } + } +} + +void vtkOpenVRRenderWindowInteractor2::HandleGripEvents(vtkEventData * ed) +{ + vtkEventDataDevice3D* edata = ed->GetAsEventDataDevice3D(); + if (!edata) + { + return; + } + + this->PointerIndex = static_cast<int>(edata->GetDevice()); + if (edata->GetAction() == vtkEventDataAction::Press) + { + this->DeviceInputDownCount[this->PointerIndex] = 1; + + this->StartingPhysicalEventPositions[this->PointerIndex][0] = + this->PhysicalEventPositions[this->PointerIndex][0]; + this->StartingPhysicalEventPositions[this->PointerIndex][1] = + this->PhysicalEventPositions[this->PointerIndex][1]; + this->StartingPhysicalEventPositions[this->PointerIndex][2] = + this->PhysicalEventPositions[this->PointerIndex][2]; + + vtkVRRenderWindow* renWin = vtkVRRenderWindow::SafeDownCast(this->RenderWindow); + renWin->GetPhysicalToWorldMatrix(this->StartingPhysicalToWorldMatrix); + + // Both controllers have the grip down, start multitouch + if (this->DeviceInputDownCount[static_cast<int>(vtkEventDataDevice::LeftController)] && + this->DeviceInputDownCount[static_cast<int>(vtkEventDataDevice::RightController)]) + { + // we do not know what the gesture is yet + this->CurrentGesture = vtkCommand::StartEvent; + } + return; + } + // end the gesture if needed + if (edata->GetAction() == vtkEventDataAction::Release) + { + this->DeviceInputDownCount[this->PointerIndex] = 0; + + if (edata->GetInput() == vtkEventDataDeviceInput::Grip) + { + if (this->CurrentGesture == vtkCommand::PinchEvent) + { + this->EndPinchEvent(); + } + if (this->CurrentGesture == vtkCommand::PanEvent) + { + this->EndPanEvent(); + } + if (this->CurrentGesture == vtkCommand::RotateEvent) + { + this->EndRotateEvent(); + } + this->CurrentGesture = vtkCommand::NoEvent; + return; + } + } +} + +//------------------------------------------------------------------------------ +void vtkOpenVRRenderWindowInteractor2::RecognizeComplexGesture(vtkEventDataDevice3D*) +{ + // Recognize gesture only if one button is pressed per controller + int lhand = static_cast<int>(vtkEventDataDevice::LeftController); + int rhand = static_cast<int>(vtkEventDataDevice::RightController); + + if (this->DeviceInputDownCount[lhand] > 1 || this->DeviceInputDownCount[lhand] == 0 || + this->DeviceInputDownCount[rhand] > 1 || this->DeviceInputDownCount[rhand] == 0) + { + this->CurrentGesture = vtkCommand::NoEvent; + return; + } + + double* posVals[2]; + double* startVals[2]; + posVals[0] = this->PhysicalEventPositions[lhand]; + posVals[1] = this->PhysicalEventPositions[rhand]; + + startVals[0] = this->StartingPhysicalEventPositions[lhand]; + startVals[1] = this->StartingPhysicalEventPositions[rhand]; + + // The meat of the algorithm + // on move events we analyze them to determine what type + // of movement it is and then deal with it. + if (this->CurrentGesture != vtkCommand::NoEvent) + { + // calculate the distances + double originalDistance = sqrt(vtkMath::Distance2BetweenPoints(startVals[0], startVals[1])); + double newDistance = sqrt(vtkMath::Distance2BetweenPoints(posVals[0], posVals[1])); + + // calculate the translations + double t0[3]; + t0[0] = posVals[0][0] - startVals[0][0]; + t0[1] = posVals[0][1] - startVals[0][1]; + t0[2] = posVals[0][2] - startVals[0][2]; + + double t1[3]; + t1[0] = posVals[1][0] - startVals[1][0]; + t1[1] = posVals[1][1] - startVals[1][1]; + t1[2] = posVals[1][2] - startVals[1][2]; + + double trans[3]; + trans[0] = (t0[0] + t1[0]) / 2.0; + trans[1] = (t0[1] + t1[1]) / 2.0; + trans[2] = (t0[2] + t1[2]) / 2.0; + + // calculate rotations + double originalAngle = vtkMath::DegreesFromRadians( + atan2((double)startVals[1][2] - startVals[0][2], (double)startVals[1][0] - startVals[0][0])); + double newAngle = vtkMath::DegreesFromRadians( + atan2((double)posVals[1][2] - posVals[0][2], (double)posVals[1][0] - posVals[0][0])); + + // angles are cyclic so watch for that, -179 and 179 are only 2 apart :) + if (newAngle - originalAngle > 180.0) + { + newAngle -= 360; + } + if (newAngle - originalAngle < -180.0) + { + newAngle += 360; + } + double angleDeviation = newAngle - originalAngle; + + // do we know what gesture we are doing yet? If not + // see if we can figure it out + if (this->CurrentGesture == vtkCommand::StartEvent) + { + // pinch is a move to/from the center point + // rotate is a move along the circumference + // pan is a move of the center point + // compute the distance along each of these axes in meters + // the first to break thresh wins + double thresh = 0.05; // in meters + + double pinchDistance = fabs(newDistance - originalDistance); + double panDistance = sqrt(trans[0] * trans[0] + trans[1] * trans[1] + trans[2] * trans[2]); + double rotateDistance = originalDistance * 3.1415926 * fabs(angleDeviation) / 180.0; + + if (pinchDistance > thresh && pinchDistance > panDistance && pinchDistance > rotateDistance) + { + this->CurrentGesture = vtkCommand::PinchEvent; + this->Scale = 1.0; + this->StartPinchEvent(); + } + else if (rotateDistance > thresh && rotateDistance > panDistance) + { + this->CurrentGesture = vtkCommand::RotateEvent; + this->Rotation = 0.0; + this->StartRotateEvent(); + } + else if (panDistance > thresh) + { + this->CurrentGesture = vtkCommand::PanEvent; + this->Translation3D[0] = 0.0; + this->Translation3D[1] = 0.0; + this->Translation3D[2] = 0.0; + this->StartPanEvent(); + } + } + // if we have found a specific type of movement then + // handle it + if (this->CurrentGesture == vtkCommand::RotateEvent) + { + this->SetRotation(angleDeviation); + this->RotateEvent(); + } + if (this->CurrentGesture == vtkCommand::PinchEvent) + { + this->SetScale(newDistance / originalDistance); + this->PinchEvent(); + } + if (this->CurrentGesture == vtkCommand::PanEvent) + { + // Vive to world axes + vtkVRRenderWindow* win = vtkVRRenderWindow::SafeDownCast(this->RenderWindow); + double* vup = win->GetPhysicalViewUp(); + double* dop = win->GetPhysicalViewDirection(); + double physicalScale = win->GetPhysicalScale(); + double vright[3]; + vtkMath::Cross(dop, vup, vright); + double wtrans[3]; + + // convert translation to world coordinates + // now adjust for scale + for (int i = 0; i < 3; i++) + { + wtrans[i] = trans[0] * vright[i] + trans[1] * vup[i] - trans[2] * dop[i]; + wtrans[i] = wtrans[i] * physicalScale; + } + + this->SetTranslation3D(wtrans); + this->PanEvent(); + } + } +} + +//------------------------------------------------------------------------------ +void vtkOpenVRRenderWindowInteractor2::AddAction( + std::string path, vtkCommand::EventIds eid, bool isAnalog) +{ + auto& am = this->ActionMap[path]; + am.EventId = eid; + am.UseFunction = false; + am.IsAnalog = isAnalog; + if (this->Initialized) + { + vr::VRInput()->GetActionHandle(path.c_str(), &am.ActionHandle); + + // "path" : "/user/hand/right/input/trackpad" + } +} + +void vtkOpenVRRenderWindowInteractor2::AddAction( + std::string path, bool isAnalog, std::function<void(vtkEventData*)> func) +{ + auto& am = this->ActionMap[path]; + am.UseFunction = true; + am.Function = func; + am.IsAnalog = isAnalog; + if (this->Initialized) + { + vr::VRInput()->GetActionHandle(path.c_str(), &am.ActionHandle); + } +} + +//------------------------------------------------------------------------------ +void vtkOpenVRRenderWindowInteractor2::Initialize() +{ + // make sure we have a RenderWindow and camera + if (!this->RenderWindow) + { + vtkErrorMacro(<< "No renderer defined!"); + return; + } + if (this->Initialized) + { + return; + } + + vtkVRRenderWindow* ren = vtkVRRenderWindow::SafeDownCast(this->RenderWindow); + int* size; + + this->Initialized = 1; + // get the info we need from the RenderingWindow + + size = ren->GetSize(); + ren->GetPosition(); + this->Enable(); + this->Size[0] = size[0]; + this->Size[1] = size[1]; + + std::string fullpath = vtksys::SystemTools::CollapseFullPath(this->ActionManifestFileName); + vr::VRInput()->SetActionManifestPath(fullpath.c_str()); + vr::VRInput()->GetActionSetHandle(this->ActionSetName.c_str(), &this->ActionsetVTK); + + // add in pose events + vr::VRInput()->GetInputSourceHandle("/user/hand/left", &this->Trackers[LeftHand].Source); + // vr::VRInput()->GetActionHandle("/actions/vtk/in/HandLeft", + // &this->Trackers[LeftHand].ActionPose); + vr::VRInput()->GetInputSourceHandle("/user/hand/right", &this->Trackers[RightHand].Source); + // vr::VRInput()->GetActionHandle( + // "/actions/vtk/in/HandRight", &this->Trackers[RightHand].ActionPose); + vr::VRInput()->GetInputSourceHandle("/user/head", &this->Trackers[Head].Source); + // vr::VRInput()->GetActionHandle("/actions/vtk/in/head", &this->Trackers[Head].ActionPose); + + this->AddAction("/actions/vtk/in/LeftGripAction", false, + [this](vtkEventData* ed) { this->HandleGripEvents(ed); }); + this->AddAction("/actions/vtk/in/RightGripAction", false, + [this](vtkEventData* ed) { this->HandleGripEvents(ed); }); + + // add extra event actions + for (auto& it : this->ActionMap) + { + vr::VRInput()->GetActionHandle(it.first.c_str(), &it.second.ActionHandle); + } +} + +//------------------------------------------------------------------------------ +int vtkOpenVRRenderWindowInteractor2::InternalCreateTimer( + int vtkNotUsed(timerId), int vtkNotUsed(timerType), unsigned long vtkNotUsed(duration)) +{ + // todo + return 0; +} + +//------------------------------------------------------------------------------ +int vtkOpenVRRenderWindowInteractor2::InternalDestroyTimer(int vtkNotUsed(platformTimerId)) +{ + // todo + return 0; +} + +//------------------------------------------------------------------------------ +// Specify the default function to be called when an interactor needs to exit. +// This callback is overridden by an instance ExitMethod that is defined. +void vtkOpenVRRenderWindowInteractor2::SetClassExitMethod(void (*f)(void*), void* arg) +{ + if (f != vtkOpenVRRenderWindowInteractor2::ClassExitMethod || + arg != vtkOpenVRRenderWindowInteractor2::ClassExitMethodArg) + { + // delete the current arg if there is a delete method + if ((vtkOpenVRRenderWindowInteractor2::ClassExitMethodArg) && + (vtkOpenVRRenderWindowInteractor2::ClassExitMethodArgDelete)) + { + (*vtkOpenVRRenderWindowInteractor2::ClassExitMethodArgDelete)( + vtkOpenVRRenderWindowInteractor2::ClassExitMethodArg); + } + vtkOpenVRRenderWindowInteractor2::ClassExitMethod = f; + vtkOpenVRRenderWindowInteractor2::ClassExitMethodArg = arg; + + // no call to this->Modified() since this is a class member function + } +} + +//------------------------------------------------------------------------------ +// Set the arg delete method. This is used to free user memory. +void vtkOpenVRRenderWindowInteractor2::SetClassExitMethodArgDelete(void (*f)(void*)) +{ + if (f != vtkOpenVRRenderWindowInteractor2::ClassExitMethodArgDelete) + { + vtkOpenVRRenderWindowInteractor2::ClassExitMethodArgDelete = f; + + // no call to this->Modified() since this is a class member function + } +} + +//------------------------------------------------------------------------------ +void vtkOpenVRRenderWindowInteractor2::PrintSelf(ostream & os, vtkIndent indent) +{ + this->Superclass::PrintSelf(os, indent); + os << indent << "StartedMessageLoop: " << this->StartedMessageLoop << endl; +} + +//------------------------------------------------------------------------------ +void vtkOpenVRRenderWindowInteractor2::ExitCallback() +{ + if (this->HasObserver(vtkCommand::ExitEvent)) + { + this->InvokeEvent(vtkCommand::ExitEvent, nullptr); + } + else if (vtkOpenVRRenderWindowInteractor2::ClassExitMethod) + { + (*vtkOpenVRRenderWindowInteractor2::ClassExitMethod)( + vtkOpenVRRenderWindowInteractor2::ClassExitMethodArg); + } + + this->TerminateApp(); +} + +//------------------------------------------------------------------------------ +vtkEventDataDevice vtkOpenVRRenderWindowInteractor2::GetPointerDevice() +{ + if (this->PointerIndex == 0) + { + return vtkEventDataDevice::RightController; + } + if (this->PointerIndex == 1) + { + return vtkEventDataDevice::LeftController; + } + return vtkEventDataDevice::Unknown; +} + +//------------------------------------------------------------------------------ +void vtkOpenVRRenderWindowInteractor2::GetStartingPhysicalToWorldMatrix( + vtkMatrix4x4 * startingPhysicalToWorldMatrix) +{ + if (!startingPhysicalToWorldMatrix) + { + return; + } + startingPhysicalToWorldMatrix->DeepCopy(this->StartingPhysicalToWorldMatrix); +} diff --git a/Source/ViewerVTK/imstkVtkOpenVRRenderWindowInteractor2.h b/Source/ViewerVTK/imstkVtkOpenVRRenderWindowInteractor2.h new file mode 100644 index 0000000000000000000000000000000000000000..f6cb2ae805baffea4be916061dbbbfc8443ccdc4 --- /dev/null +++ b/Source/ViewerVTK/imstkVtkOpenVRRenderWindowInteractor2.h @@ -0,0 +1,247 @@ +/*========================================================================= + + Program: Visualization Toolkit + Module: vtkOpenVRRenderWindowInteractor.h + + Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen + All rights reserved. + See Copyright.txt or http://www.kitware.com/Copyright.htm for details. + + This software is distributed WITHOUT ANY WARRANTY; without even + the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + PURPOSE. See the above copyright notice for more information. + +=========================================================================*/ +/** + * @class vtkOpenVRRenderWindowInteractor2 + * @brief implements OpenVR specific functions + * required by vtkOpenVRRenderWindowInteractor2. + * + * + */ + +#ifndef vtkOpenVRRenderWindowInteractor2_h +#define vtkOpenVRRenderWindowInteractor2_h + +#include "vtkEventData.h" // for ivar +#include "vtkNew.h" // ivars +#include "vtkRenderWindowInteractor3D.h" +#include "vtkRenderingOpenVRModule.h" // For export macro +#include <functional> // for ivar +#include <map> // for ivar +#include <openvr.h> // for ivar +#include <string> // for ivar +#include <tuple> // for ivar + +class vtkTransform; +class vtkMatrix4x4; +class vtkOpenVROverlay; +class vtkOpenVRRenderWindow; + +class vtkOpenVRRenderWindowInteractor2 : public vtkRenderWindowInteractor3D +{ +public: + /** + * Construct object so that light follows camera motion. + */ + static vtkOpenVRRenderWindowInteractor2* New(); + + vtkTypeMacro(vtkOpenVRRenderWindowInteractor2, vtkRenderWindowInteractor3D); + void PrintSelf(ostream& os, vtkIndent indent); + + /** + * Initialize the event handler + */ + virtual void Initialize(); + + ///@{ + /** + * Methods to set the default exit method for the class. This method is + * only used if no instance level ExitMethod has been defined. It is + * provided as a means to control how an interactor is exited given + * the various language bindings (Win32, etc.). + */ + static void SetClassExitMethod(void (*f)(void*), void* arg); + static void SetClassExitMethodArgDelete(void (*f)(void*)); + ///@} + + /** + * These methods correspond to the Exit, User and Pick + * callbacks. They allow for the Style to invoke them. + */ + virtual void ExitCallback(); + + ///@{ + /** + * Set/Get the optional translation to map world coordinates into the + * 3D physical space (meters, 0,0,0). + */ + virtual void SetPhysicalTranslation(vtkCamera*, double, double, double); + virtual double* GetPhysicalTranslation(vtkCamera*); + virtual void SetPhysicalScale(double); + virtual double GetPhysicalScale(); + ///@} + + /** + * Run the event loop and return. This is provided so that you can + * implement your own event loop but yet use the vtk event handling as + * well. + */ + void ProcessEvents() override; + + /** + * Process OpenVR events such as updating poses + * \param render window + * \param renderer + * \param Whether or not to perform a render after processing VR events + */ + virtual void DoOneEvent(vtkOpenVRRenderWindow* renWin, vtkRenderer* ren, bool doRender); + + /* + * Return the pointer index as a device + */ + vtkEventDataDevice GetPointerDevice(); + + /* + * Convert a device pose to pose matrices + * \param poseMatrixPhysical Optional output pose matrix in physical frame + * \param poseMatrixWorld Optional output pose matrix in world frame + */ + void ConvertOpenVRPoseToMatrices(const vr::TrackedDevicePose_t& tdPose, + vtkMatrix4x4* poseMatrixWorld, vtkMatrix4x4* poseMatrixPhysical = nullptr); + + /* + * Convert a device pose to a world coordinate position and orientation + * \param pos Output world position + * \param wxyz Output world orientation quaternion + * \param ppos Output physical position + * \param wdir Output world view direction (-Z) + */ + void ConvertPoseToWorldCoordinates(const vr::TrackedDevicePose_t& tdPose, double pos[3], + double wxyz[4], double ppos[3], double wdir[3]); + void ConvertPoseMatrixToWorldCoordinates( + const float poseMatrix[3][4], double pos[3], double wxyz[4], double ppos[3], double wdir[3]); + + ///@{ + /** + * Get the latest touchpad or joystick position for a device + */ + // void GetTouchPadPosition(vtkEventDataDevice, vtkEventDataDeviceInput, float[3]) override; + ///@} + + /* + * Return starting physical to world matrix + */ + void GetStartingPhysicalToWorldMatrix(vtkMatrix4x4* startingPhysicalToWorldMatrix); + + ///@{ + /** + * Assign an event or std::function to an event path + */ + void AddAction(std::string path, vtkCommand::EventIds, bool isAnalog); + void AddAction(std::string path, bool isAnalog, std::function<void(vtkEventData*)>); + ///@} + // add an event action + + ///@{ + /** + * Set/Get the json file describing action bindings for events + * See https://github.com/ValveSoftware/openvr/wiki/Action-manifest + */ + vtkGetMacro(ActionManifestFileName, std::string); + vtkSetMacro(ActionManifestFileName, std::string); + ///@} + + ///@{ + /** + * Set/Get the json file describing action set to use + */ + vtkGetMacro(ActionSetName, std::string); + vtkSetMacro(ActionSetName, std::string); + ///@} + +protected: + vtkOpenVRRenderWindowInteractor2(); + ~vtkOpenVRRenderWindowInteractor2() override; + + ///@{ + /** + * Class variables so an exit method can be defined for this class + * (used to set different exit methods for various language bindings, + * i.e. java, Win32) + */ + static void (*ClassExitMethod)(void*); + static void (*ClassExitMethodArgDelete)(void*); + static void* ClassExitMethodArg; + ///@} + + ///@{ + /** + * Win32-specific internal timer methods. See the superclass for detailed + * documentation. + */ + virtual int InternalCreateTimer(int timerId, int timerType, unsigned long duration); + virtual int InternalDestroyTimer(int platformTimerId); + ///@} + + /** + * This will start up the event loop and never return. If you + * call this method it will loop processing events until the + * application is exited. + */ + virtual void StartEventLoop(); + + /** + * Handle multitouch events. Multitouch events recognition starts when + * both controllers the trigger pressed. + */ + int DeviceInputDownCount[vtkEventDataNumberOfDevices]; + virtual void RecognizeComplexGesture(vtkEventDataDevice3D* edata); + + /** + * Store physical to world matrix at the start of a multi-touch gesture + */ + vtkNew<vtkMatrix4x4> StartingPhysicalToWorldMatrix; + + class ActionData + { + public: + vr::VRActionHandle_t ActionHandle; + vtkCommand::EventIds EventId; + std::function<void(vtkEventData*)> Function; + bool UseFunction = false; + bool IsAnalog = false; + }; + + std::map<std::string, ActionData> ActionMap; + + std::string ActionManifestFileName; + std::string ActionSetName; + + vr::VRActionSetHandle_t ActionsetVTK = vr::k_ulInvalidActionSetHandle; + + enum TrackerEnum + { + LeftHand = 0, + RightHand, + Head, + NumberOfTrackers + }; + + struct TrackerActions + { + vr::VRInputValueHandle_t Source = vr::k_ulInvalidInputValueHandle; + // vr::VRActionHandle_t ActionPose = vr::k_ulInvalidActionHandle; + // vr::InputPoseActionData_t LastPoseData; + vr::TrackedDevicePose_t LastPose; + }; + TrackerActions Trackers[NumberOfTrackers]; + + void HandleGripEvents(vtkEventData* ed); + +private: + vtkOpenVRRenderWindowInteractor2(const vtkOpenVRRenderWindowInteractor2&) = delete; + void operator=(const vtkOpenVRRenderWindowInteractor2&) = delete; +}; + +#endif \ No newline at end of file