diff --git a/CMakeLists.txt b/CMakeLists.txt index 54d36ae..a96fff8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,31 @@ mark_as_superbuild(Slicer_DIR) find_package(Git REQUIRED) mark_as_superbuild(GIT_EXECUTABLE) +#----------------------------------------------------------------------------- +# Options + +# OpenVR +set(_default ON) +set(_reason) +if(NOT DEFINED SlicerVirtualReality_HAS_OPENVR_SUPPORT AND APPLE) + set(_default OFF) + set(_reason " (OpenVR not supported on macOS)") +endif() +option(SlicerVirtualReality_HAS_OPENVR_SUPPORT "Build OpenVR XR runtime" ${_default}) +mark_as_superbuild(SlicerVirtualReality_HAS_OPENVR_SUPPORT) +message(STATUS "") +message(STATUS "Setting SlicerVirtualReality_HAS_OPENVR_SUPPORT to ${SlicerVirtualReality_HAS_OPENVR_SUPPORT}${_reason}") +message(STATUS "") + +# OpenXR +set(_default ON) +set(_reason) +option(SlicerVirtualReality_HAS_OPENXR_SUPPORT "Build OpenXR XR runtime" ${_default}) +mark_as_superbuild(SlicerVirtualReality_HAS_OPENXR_SUPPORT) +message(STATUS "") +message(STATUS "Setting SlicerVirtualReality_HAS_OPENXR_SUPPORT to ${SlicerVirtualReality_HAS_OPENXR_SUPPORT}${_reason}") +message(STATUS "") + #----------------------------------------------------------------------------- # SuperBuild setup option(${EXTENSION_NAME}_SUPERBUILD "Build ${EXTENSION_NAME} and the projects it depends on." ON) @@ -61,8 +86,21 @@ add_subdirectory(VirtualReality) #----------------------------------------------------------------------------- set(SlicerVirtualReality_CUSTOM_CONFIG "####### Expanded from \@SlicerVirtualReality_CUSTOM_CONFIG\@ ####### -set(vtkRenderingOpenVR_DIR \"${vtkRenderingOpenVR_DIR}\") -find_package(vtkRenderingOpenVR REQUIRED) + +set(vtkRenderingVR_DIR \"${vtkRenderingVR_DIR}\") +find_package(vtkRenderingVR REQUIRED) + +set(SlicerVirtualReality_HAS_OPENVR_SUPPORT ${SlicerVirtualReality_HAS_OPENVR_SUPPORT}) +if(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + set(vtkRenderingOpenVR_DIR \"${vtkRenderingOpenVR_DIR}\") + find_package(vtkRenderingOpenVR REQUIRED) +endif() + +set(SlicerVirtualReality_HAS_OPENXR_SUPPORT ${SlicerVirtualReality_HAS_OPENXR_SUPPORT}) +if(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + set(vtkRenderingOpenXR_DIR \"${vtkRenderingOpenXR_DIR}\") + find_package(vtkRenderingOpenXR REQUIRED) +endif() ################################################## ") include(${Slicer_EXTENSION_GENERATE_CONFIG}) @@ -71,27 +109,64 @@ include(${Slicer_EXTENSION_GENERATE_CONFIG}) # Install OpenVR set(_platform) if(${CMAKE_SYSTEM_NAME} MATCHES "Windows") - # ${OpenVR_LIBRARY} contains import library which does not have to be installed. - # Instead, the dll must be added to the package. - install(FILES ${OpenVR_LIBRARY_PATHS_LAUNCHER_BUILD}/openvr_api.dll - DESTINATION ${Slicer_THIRDPARTY_LIB_DIR} - COMPONENT RuntimeLibraries - ) + if(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + # ${OpenVR_LIBRARY} contains import library which does not have to be installed. + # Instead, the dll must be added to the package. + install(FILES ${OpenVR_LIBRARY_PATHS_LAUNCHER_BUILD}/openvr_api.dll + DESTINATION ${Slicer_THIRDPARTY_LIB_DIR} + COMPONENT RuntimeLibraries + ) + endif() + + if(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + # ${OpenXR_LIBRARY} contains import library which does not have to be installed. + # Instead, the dll must be added to the package. + set(_library "${OpenXR-SDK_LIBRARY_PATHS_LAUNCHER_BUILD}/openxr_loader.dll") + # Since the launcher settings include the placeholder , let's + # replace if with the corresponding generator expression. + install(FILES ${_library} + DESTINATION ${Slicer_THIRDPARTY_LIB_DIR} + COMPONENT RuntimeLibraries + ) + endif() else() - install(FILES ${OpenVR_LIBRARY} - DESTINATION ${Slicer_THIRDPARTY_LIB_DIR} - COMPONENT RuntimeLibraries - ) + if(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + install(FILES ${OpenVR_LIBRARY} + DESTINATION ${Slicer_THIRDPARTY_LIB_DIR} + COMPONENT RuntimeLibraries + ) + endif() + + if(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + install(FILES ${OpenXR_LIBRARY} + DESTINATION ${Slicer_INSTALL_THIRDPARTY_LIB_DIR} + COMPONENT RuntimeLibraries + ) + endif() endif() #----------------------------------------------------------------------------- set(EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS) + list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${vtkRenderingVR_DIR};vtkRenderingVR;runtime;/") -list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${vtkRenderingOpenVR_DIR};vtkRenderingOpenVR;runtime;/") if(Slicer_USE_PYTHONQT) list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${vtkRenderingVR_DIR};vtkRenderingVR;python;/") - list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${vtkRenderingOpenVR_DIR};vtkRenderingOpenVR;python;/") endif() + +if(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${vtkRenderingOpenVR_DIR};vtkRenderingOpenVR;runtime;/") + if(Slicer_USE_PYTHONQT) + list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${vtkRenderingOpenVR_DIR};vtkRenderingOpenVR;python;/") + endif() +endif() + +if(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${vtkRenderingOpenXR_DIR};vtkRenderingOpenXR;runtime;/") + if(Slicer_USE_PYTHONQT) + list(APPEND EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS "${vtkRenderingOpenXR_DIR};vtkRenderingOpenXR;python;/") + endif() +endif() + set(${EXTENSION_NAME}_CPACK_INSTALL_CMAKE_PROJECTS "${EXTENSION_CPACK_INSTALL_CMAKE_PROJECTS}" CACHE STRING "List of external projects to install" FORCE) #----------------------------------------------------------------------------- diff --git a/SuperBuild/External_OpenXR-SDK.cmake b/SuperBuild/External_OpenXR-SDK.cmake new file mode 100644 index 0000000..7561665 --- /dev/null +++ b/SuperBuild/External_OpenXR-SDK.cmake @@ -0,0 +1,113 @@ + +set(proj OpenXR-SDK) + +set(${proj}_DEPENDENCIES "") + +# Include dependent projects if any +ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj DEPENDS_VAR ${proj}_DEPENDENCIES) + +if(${SUPERBUILD_TOPLEVEL_PROJECT}_USE_SYSTEM_${proj}) + unset(OpenXR_INCLUDE_DIR CACHE) + unset(OpenXR_LIBRARY CACHE) + find_package(OpenXR REQUIRED) + if(NOT DEFINED OpenXR_LIBRARY) + # Since the OpenXRConfig.cmake file provided by OpenXR-SDK does not set OpenXR_LIBRARY, its + # value may be retrieved from the OpenXR::OpenXR target. + # TODO + endif() +endif() + +# Sanity checks +if(DEFINED OpenXR_INCLUDE_DIR AND NOT EXISTS ${OpenXR_INCLUDE_DIR}) + message(FATAL_ERROR "OpenXR_INCLUDE_DIR variable is defined but corresponds to nonexistent directory") +endif() +if(DEFINED OpenXR_LIBRARY AND NOT EXISTS ${OpenXR_LIBRARY}) + message(FATAL_ERROR "OpenXR_LIBRARY variable is defined but corresponds to nonexistent path") +endif() + +if((NOT OpenXR_INCLUDE_DIR OR NOT OpenXR_LIBRARY) + AND NOT ${SUPERBUILD_TOPLEVEL_PROJECT}_USE_SYSTEM_${proj}) + + ExternalProject_SetIfNotDefined( + ${SUPERBUILD_TOPLEVEL_PROJECT}_${proj}_GIT_REPOSITORY + "https://github.com/KhronosGroup/OpenXR-SDK.git" + QUIET + ) + + ExternalProject_SetIfNotDefined( + ${SUPERBUILD_TOPLEVEL_PROJECT}_${proj}_GIT_TAG + "release-1.0.26" + QUIET + ) + + set(EP_SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj}) + set(EP_BINARY_DIR ${CMAKE_BINARY_DIR}/${proj}-build) + set(EP_INSTALL_DIR ${CMAKE_BINARY_DIR}/${proj}-install) + + ExternalProject_Add(${proj} + ${${proj}_EP_ARGS} + GIT_REPOSITORY "${${SUPERBUILD_TOPLEVEL_PROJECT}_${proj}_GIT_REPOSITORY}" + GIT_TAG "${${SUPERBUILD_TOPLEVEL_PROJECT}_${proj}_GIT_TAG}" + SOURCE_DIR ${EP_SOURCE_DIR} + BINARY_DIR ${EP_BINARY_DIR} + INSTALL_DIR ${EP_INSTALL_DIR} + CMAKE_CACHE_ARGS + # Compiler settings + -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} + -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} + -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} + -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} + -DCMAKE_CXX_STANDARD:STRING=${CMAKE_CXX_STANDARD} + -DCMAKE_CXX_STANDARD_REQUIRED:BOOL=${CMAKE_CXX_STANDARD_REQUIRED} + -DCMAKE_CXX_EXTENSIONS:BOOL=${CMAKE_CXX_EXTENSIONS} + # Options + -DBUILD_TESTING:BOOL=OFF + -DDYNAMIC_LOADER:BOOL=ON + # Install directories + -DCMAKE_INSTALL_PREFIX:PATH= + ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS} + DEPENDS + ${${proj}_DEPENDENCIES} + ) + set(OpenXR_DIR "${EP_INSTALL_DIR}/lib/cmake/openxr") + + set(OpenXR_INCLUDE_DIR "${EP_INSTALL_DIR}/include/openxr") + + if(WIN32) + set(OpenXR_LIBRARY "${EP_INSTALL_DIR}/lib/openxr_loader.lib") + elseif(APPLE) + set(OpenXR_LIBRARY "${EP_INSTALL_DIR}/lib/libopenxr_loader.dylib") + elseif(UNIX) + set(OpenXR_LIBRARY "${EP_INSTALL_DIR}/lib/libopenxr_loader.so") + endif() + + #----------------------------------------------------------------------------- + # Launcher setting specific to build tree + + # library paths + set(${proj}_LIBRARY_PATHS_LAUNCHER_BUILD + ${EP_BINARY_DIR}/src/loader/ + ) + mark_as_superbuild( + VARS ${proj}_LIBRARY_PATHS_LAUNCHER_BUILD + LABELS "LIBRARY_PATHS_LAUNCHER_BUILD" + ) + + #----------------------------------------------------------------------------- + # Launcher setting specific to install tree + + # NA + +else() + ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) +endif() + +ExternalProject_Message(${proj} "OpenXR_INCLUDE_DIR:${OpenXR_INCLUDE_DIR}") +ExternalProject_Message(${proj} "OpenXR_LIBRARY:${OpenXR_LIBRARY}") + +mark_as_superbuild( + VARS + OpenXR_INCLUDE_DIR:PATH + OpenXR_LIBRARY:FILEPATH + ) + diff --git a/SuperBuild/External_vtkRenderingOpenXR.cmake b/SuperBuild/External_vtkRenderingOpenXR.cmake new file mode 100644 index 0000000..4626107 --- /dev/null +++ b/SuperBuild/External_vtkRenderingOpenXR.cmake @@ -0,0 +1,123 @@ +#----------------------------------------------------------------------------- +# Build VTK Rendering OpenXR module, pointing it to Slicer's VTK and the OpenXR +# libraries also built by this extension. + +set(proj vtkRenderingOpenXR) + +# Set dependency list +set(${proj}_DEPENDS + OpenXR-SDK + vtkRenderingVR + ) + +# Include dependent projects if any +ExternalProject_Include_Dependencies(${proj} PROJECT_VAR proj) + +if(${SUPERBUILD_TOPLEVEL_PROJECT}_USE_SYSTEM_${proj}) + message(FATAL_ERROR "Enabling ${SUPERBUILD_TOPLEVEL_PROJECT}_USE_SYSTEM_${proj} is not supported !") +endif() + +# Sanity checks +if(DEFINED ${proj}_DIR AND NOT EXISTS ${${proj}_DIR}) + message(FATAL_ERROR "${proj}_DIR [${${proj}_DIR}] variable is defined but corresponds to nonexistent directory") +endif() + +if(NOT DEFINED ${proj}_DIR AND NOT ${SUPERBUILD_TOPLEVEL_PROJECT}_USE_SYSTEM_${proj}) + + set(EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS) + if(VTK_WRAP_PYTHON) + list(APPEND EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS + -DPYTHON_EXECUTABLE:FILEPATH=${PYTHON_EXECUTABLE} + -DPYTHON_INCLUDE_DIR:PATH=${PYTHON_INCLUDE_DIR} + -DPYTHON_LIBRARY:FILEPATH=${PYTHON_LIBRARY} + # Required by FindPython3 CMake module used by VTK + -DPython3_ROOT_DIR:PATH=${Python3_ROOT_DIR} + -DPython3_INCLUDE_DIR:PATH=${Python3_INCLUDE_DIR} + -DPython3_LIBRARY:FILEPATH=${Python3_LIBRARY} + -DPython3_LIBRARY_DEBUG:FILEPATH=${Python3_LIBRARY_DEBUG} + -DPython3_LIBRARY_RELEASE:FILEPATH=${Python3_LIBRARY_RELEASE} + -DPython3_EXECUTABLE:FILEPATH=${Python3_EXECUTABLE} + ) + endif() + + if(NOT EXISTS ${VTKExternalModule_SOURCE_DIR}) + message(FATAL_ERROR "VTKExternalModule_SOURCE_DIR [${VTKExternalModule_SOURCE_DIR}] variable is set to a nonexistent directory") + endif() + + set(VTK_SOURCE_DIR ${VTK_DIR}/../VTK) + ExternalProject_Message(${proj} "VTK_SOURCE_DIR:${VTK_SOURCE_DIR}") + + set(_module_subdir Rendering/OpenXR) + set(_module_name RenderingOpenXR) + + set(EP_SOURCE_DIR ${VTK_SOURCE_DIR}/${_module_subdir}) + set(EP_BINARY_DIR ${CMAKE_BINARY_DIR}/${proj}-build) + + # The "vtk_openxr_actions.json" and "vtk_openxr_binding_*.json" files + # are then installed into "${_manifest_install_dir}/xr_actions/" directory. + set(_openxr_manifest_install_dir ${Slicer_INSTALL_THIRDPARTY_LIB_DIR}) + + ExternalProject_Add(${proj} + ${${proj}_EP_ARGS} + DOWNLOAD_COMMAND "" + SOURCE_DIR ${VTKExternalModule_SOURCE_DIR} + BINARY_DIR ${EP_BINARY_DIR} + INSTALL_COMMAND "" + CMAKE_CACHE_ARGS + # VTKExternalModule + -DVTK_MODULE_NAME:STRING=${_module_name} + -DVTK_MODULE_SOURCE_DIR:PATH=${EP_SOURCE_DIR} + -DVTK_MODULE_CMAKE_MODULE_PATH:PATH=${VTK_SOURCE_DIR}/CMake + -DOpenXR_FIND_PACKAGE_VARS:STRING=OpenXR_INCLUDE_DIR;OpenXR_LIBRARY + # vtkRenderingOpenXR + -DCMAKE_CXX_COMPILER:FILEPATH=${CMAKE_CXX_COMPILER} + -DCMAKE_CXX_FLAGS:STRING=${ep_common_cxx_flags} + -DCMAKE_C_COMPILER:FILEPATH=${CMAKE_C_COMPILER} + -DCMAKE_C_FLAGS:STRING=${ep_common_c_flags} + -DBUILD_TESTING:BOOL=OFF + -DCMAKE_RUNTIME_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_BIN_DIR} + -DCMAKE_LIBRARY_OUTPUT_DIRECTORY:PATH=${CMAKE_BINARY_DIR}/${Slicer_THIRDPARTY_LIB_DIR} + -DCMAKE_INSTALL_BINDIR:STRING=${Slicer_INSTALL_THIRDPARTY_BIN_DIR} + -DCMAKE_INSTALL_LIBDIR:STRING=${Slicer_INSTALL_THIRDPARTY_LIB_DIR} + -DCMAKE_INSTALL_DATAROOTDIR:STRING=${_openxr_manifest_install_dir} + -DCMAKE_MACOSX_RPATH:BOOL=0 + # Required to find VTK + -DVTK_DIR:PATH=${VTK_DIR} + # Required to find vtkRenderingVR + -DvtkRenderingVR_DIR:PATH=${vtkRenderingVR_DIR} + # Required to find OpenXR + -DOpenXR_INCLUDE_DIR:PATH=${OpenXR_INCLUDE_DIR} + -DOpenXR_LIBRARY:PATH=${OpenXR_LIBRARY} + ${EXTERNAL_PROJECT_OPTIONAL_CMAKE_CACHE_ARGS} + DEPENDS + ${${proj}_DEPENDS} + ) + + ExternalProject_AlwaysConfigure(${proj}) + + set(${proj}_DIR ${EP_BINARY_DIR}) + + #----------------------------------------------------------------------------- + # Launcher setting specific to build tree + + # pythonpath + set(${proj}_PYTHONPATH_LAUNCHER_BUILD + ${${proj}_DIR}/${Slicer_INSTALL_THIRDPARTY_LIB_DIR}/${PYTHON_SITE_PACKAGES_SUBDIR}/vtkmodules + ) + mark_as_superbuild( + VARS ${proj}_PYTHONPATH_LAUNCHER_BUILD + LABELS "PYTHONPATH_LAUNCHER_BUILD" + ) + + #----------------------------------------------------------------------------- + # Launcher setting specific to install tree + + # NA + +else() + ExternalProject_Add_Empty(${proj} DEPENDS ${${proj}_DEPENDS}) +endif() + +mark_as_superbuild(VARS ${proj}_DIR:PATH) +ExternalProject_Message(${proj} "${proj}_DIR:${${proj}_DIR}") + diff --git a/SuperBuildPrerequisites.cmake b/SuperBuildPrerequisites.cmake index 9181d9a..c3ed558 100644 --- a/SuperBuildPrerequisites.cmake +++ b/SuperBuildPrerequisites.cmake @@ -9,10 +9,11 @@ if(DEFINED slicersources_SOURCE_DIR AND NOT DEFINED Slicer_SOURCE_DIR) set(Slicer_SOURCE_DIR ${slicersources_SOURCE_DIR}) endif() -if(APPLE) - set(SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES "") - message(STATUS "SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES:${SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES}") - return() +if(NOT DEFINED SlicerVirtualReality_HAS_OPENVR_SUPPORT) + message(FATAL_ERROR "SlicerVirtualReality_HAS_OPENVR_SUPPORT is not set") +endif() +if(NOT DEFINED SlicerVirtualReality_HAS_OPENXR_SUPPORT) + message(FATAL_ERROR "SlicerVirtualReality_HAS_OPENXR_SUPPORT is not set") endif() # Set list of dependencies to ensure the custom application bundling this @@ -20,15 +21,33 @@ endif() # build external projects associated with VTK modules enabled below. if(DEFINED Slicer_SOURCE_DIR) # Extension is bundled in a custom application - set(SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES - OpenVR - ) + set(SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES "") + if(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + list(APPEND SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES + OpenVR + ) + endif() + if(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + list(APPEND SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES + OpenXR-SDK + ) + endif() else() # Extension is build standalone against Slicer itself built # against VTK without the relevant modules enabled. set(SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES - vtkRenderingOpenVR + vtkRenderingVR ) + if(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + list(APPEND SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES + vtkRenderingOpenVR + ) + endif() + if(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + list(APPEND SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES + vtkRenderingOpenXR + ) + endif() endif() message(STATUS "SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES:${SlicerVirtualReality_EXTERNAL_PROJECT_DEPENDENCIES}") @@ -38,23 +57,44 @@ if(NOT DEFINED Slicer_SOURCE_DIR) # VTKExternalModule is required to configure these external projects: # - vtkRenderingVR # - vtkRenderingOpenVR + # - vtkRenderingOpenXR include(${SlicerVirtualReality_SOURCE_DIR}/FetchVTKExternalModule.cmake) else() # Extension is bundled in a custom application # Additional external project dependencies - ExternalProject_Add_Dependencies(VTK - DEPENDS - OpenVR - ) + if(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + ExternalProject_Add_Dependencies(VTK + DEPENDS + OpenVR + ) + endif() + if(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + ExternalProject_Add_Dependencies(VTK + DEPENDS + OpenXR-SDK + ) + endif() # Additional external project options - set(VTK_MODULE_ENABLE_VTK_RenderingOpenVR YES) + set(VTK_MODULE_ENABLE_VTK_RenderingVR YES) + if(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + set(VTK_MODULE_ENABLE_VTK_RenderingOpenVR YES) + else() + set(VTK_MODULE_ENABLE_VTK_RenderingOpenVR NO) + endif() + if(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + set(VTK_MODULE_ENABLE_VTK_RenderingOpenXR YES) + else() + set(VTK_MODULE_ENABLE_VTK_RenderingOpenXR NO) + endif() mark_as_superbuild( VARS + VTK_MODULE_ENABLE_VTK_RenderingVR:STRING VTK_MODULE_ENABLE_VTK_RenderingOpenVR:STRING + VTK_MODULE_ENABLE_VTK_RenderingOpenXR:STRING PROJECTS VTK ) diff --git a/VirtualReality/CMakeLists.txt b/VirtualReality/CMakeLists.txt index ac10675..f081228 100644 --- a/VirtualReality/CMakeLists.txt +++ b/VirtualReality/CMakeLists.txt @@ -6,7 +6,13 @@ set(MODULE_TITLE "Virtual Reality") string(TOUPPER ${MODULE_NAME} MODULE_NAME_UPPER) #----------------------------------------------------------------------------- -find_package(vtkRenderingOpenVR REQUIRED) +find_package(vtkRenderingVR REQUIRED) +if(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + find_package(vtkRenderingOpenVR REQUIRED) +endif() +if(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + find_package(vtkRenderingOpenXR REQUIRED) +endif() #----------------------------------------------------------------------------- add_subdirectory(MRML) diff --git a/VirtualReality/Logic/vtkSlicerVirtualRealityLogic.cxx b/VirtualReality/Logic/vtkSlicerVirtualRealityLogic.cxx index e2a2776..1f56397 100644 --- a/VirtualReality/Logic/vtkSlicerVirtualRealityLogic.cxx +++ b/VirtualReality/Logic/vtkSlicerVirtualRealityLogic.cxx @@ -15,10 +15,12 @@ ==============================================================================*/ -// VirtualReality Logic includes -#include "vtkMRMLVirtualRealityViewNode.h" +// VR Logic includes #include "vtkSlicerVirtualRealityLogic.h" +// VR MRML includes +#include "vtkMRMLVirtualRealityViewNode.h" + // MRML includes #include #include @@ -26,10 +28,17 @@ #include // Slicer includes -#include "vtkSlicerVolumeRenderingLogic.h" +#include +#include // For Slicer_THIRDPARTY_LIB_DIR + +// VTK/Rendering/VR includes +#include +#include // VTK includes #include +#include +#include #include #include @@ -197,41 +206,35 @@ void vtkSlicerVirtualRealityLogic::ProcessMRMLNodesEvents(vtkObject* caller, uns } //----------------------------------------------------------------------------- -void vtkSlicerVirtualRealityLogic::SetVirtualRealityConnected(bool connect) +void vtkSlicerVirtualRealityLogic::SetVirtualRealityXRRuntime(vtkMRMLVirtualRealityViewNode::XRRuntimeType id) { - vtkMRMLScene* scene = this->GetMRMLScene(); - if (!scene) + this->InitializeActiveViewNode(); + if (this->ActiveViewNode) { - vtkErrorMacro("SetVirtualRealityConnected: Invalid MRML scene"); - return; + this->ActiveViewNode->SetXRRuntime(id); } +} +//----------------------------------------------------------------------------- +vtkMRMLVirtualRealityViewNode::XRRuntimeType vtkSlicerVirtualRealityLogic::GetVirtualRealityXRRuntime() +{ + if (!this->ActiveViewNode) + { + return vtkMRMLVirtualRealityViewNode::UndefinedXRRuntime; + } + return this->ActiveViewNode->GetXRRuntime(); +} + +//----------------------------------------------------------------------------- +void vtkSlicerVirtualRealityLogic::SetVirtualRealityConnected(bool connect) +{ if (connect) { - if (!this->ActiveViewNode) - { - // Check if there is a VirtualReality view node in the scene, in case the scene has been loaded - // from file and VR view properties has been changed - if (scene->GetNumberOfNodesByClass("vtkMRMLVirtualRealityViewNode") > 0) - { - // Use the first one if any found - this->SetActiveViewNode( - vtkMRMLVirtualRealityViewNode::SafeDownCast(scene->GetNthNodeByClass(0, "vtkMRMLVirtualRealityViewNode"))); - } - else - { - vtkMRMLVirtualRealityViewNode* newViewNode = this->AddVirtualRealityViewNode(); - this->SetActiveViewNode(newViewNode); - } - } + this->InitializeActiveViewNode(); if (this->ActiveViewNode) { this->ActiveViewNode->SetVisibility(1); } - else - { - vtkErrorMacro("Failed to create virtual reality view node"); - } } else { @@ -294,6 +297,38 @@ bool vtkSlicerVirtualRealityLogic::GetVirtualRealityActive() return (this->ActiveViewNode->GetVisibility() != 0 && this->ActiveViewNode->GetActive() != 0); } +//----------------------------------------------------------------------------- +void vtkSlicerVirtualRealityLogic::InitializeActiveViewNode() +{ + vtkMRMLScene* scene = this->GetMRMLScene(); + if (!scene) + { + vtkErrorMacro("InitializeActiveViewNode: Invalid MRML scene"); + return; + } + + if (!this->ActiveViewNode) + { + // Check if there is a VirtualReality view node in the scene, in case the scene has been loaded + // from file and VR view properties has been changed + if (scene->GetNumberOfNodesByClass("vtkMRMLVirtualRealityViewNode") > 0) + { + // Use the first one if any found + this->SetActiveViewNode( + vtkMRMLVirtualRealityViewNode::SafeDownCast(scene->GetNthNodeByClass(0, "vtkMRMLVirtualRealityViewNode"))); + } + else + { + vtkMRMLVirtualRealityViewNode* newViewNode = this->AddVirtualRealityViewNode(); + this->SetActiveViewNode(newViewNode); + } + } + if (!this->ActiveViewNode) + { + vtkErrorMacro("Failed to create virtual reality view node"); + } +} + //--------------------------------------------------------------------------- void vtkSlicerVirtualRealityLogic::SetDefaultReferenceView() { @@ -414,3 +449,345 @@ void vtkSlicerVirtualRealityLogic::OptimizeSceneForVirtualReality() vtkMRMLSegmentationDisplayNode::SafeDownCast(defaultSegmentationDisplayNode)->SetVisibility2DFill(0); vtkMRMLSegmentationDisplayNode::SafeDownCast(defaultSegmentationDisplayNode)->SetVisibility2DOutline(0); } + +// -------------------------------------------------------------------------- +bool vtkSlicerVirtualRealityLogic::ShouldConsiderQuickViewMotion( + double motionSensitivity, double physicalScale, double elapsedTimeInSec, + double lastViewPos[3], double lastViewDir[3], double lastViewUp[3], + double viewPos[3], double viewDir[3], double viewUp[3]) +{ + if (motionSensitivity > 0.999) + { + return true; + } + else if (motionSensitivity <= 0.001) + { + return false; + } + else if (elapsedTimeInSec < 3.0) // don't consider stale measurements + { + // Compute limits + + // Limit scale: + // sensitivity = 0 -> limit = 10.0x + // sensitivity = 50% -> limit = 1.0x + // sensitivity = 100% -> limit = 0.1x + const double limitScale = pow(100, 0.5 - motionSensitivity); + const double angularSpeedLimitRadiansPerSec = vtkMath::RadiansFromDegrees(5.0 * limitScale); + const double translationSpeedLimitMmPerSec = 100.0 * limitScale; + + // Compute change speed for viewPos, viewDir, and viewUp based on last and current view properties + + // Physical scale = 100 if virtual objects are real-world size; <100 if virtual objects are larger + + const double viewDirectionChangeSpeed = vtkMath::AngleBetweenVectors(lastViewDir, viewDir) / elapsedTimeInSec; + const double viewUpChangeSpeed = vtkMath::AngleBetweenVectors(lastViewUp, viewUp) / elapsedTimeInSec; + const double viewTranslationSpeedMmPerSec = + physicalScale * 0.01 * sqrt(vtkMath::Distance2BetweenPoints(lastViewPos, viewPos)) / elapsedTimeInSec; + + if (viewDirectionChangeSpeed < angularSpeedLimitRadiansPerSec + && viewUpChangeSpeed < angularSpeedLimitRadiansPerSec + && viewTranslationSpeedMmPerSec < translationSpeedLimitMmPerSec) + { + return false; + } + } + + return true; // Default +} + +//--------------------------------------------------------------------------- +bool vtkSlicerVirtualRealityLogic::CalculateCombinedControllerPose( + vtkMatrix4x4* controller0Pose, vtkMatrix4x4* controller1Pose, vtkMatrix4x4* combinedPose) +{ + if (!controller0Pose || !controller1Pose || !combinedPose) + { + return false; + } + + // The position will be the average position + double controllerCenterPos[3] = { + (controller0Pose->GetElement(0,3) + controller1Pose->GetElement(0,3)) / 2.0, + (controller0Pose->GetElement(1,3) + controller1Pose->GetElement(1,3)) / 2.0, + (controller0Pose->GetElement(2,3) + controller1Pose->GetElement(2,3)) / 2.0 }; + + // Scaling will be the distance between the two controllers + double controllerDistance = sqrt( + (controller0Pose->GetElement(0,3) - controller1Pose->GetElement(0,3)) + * (controller0Pose->GetElement(0,3) - controller1Pose->GetElement(0,3)) + + (controller0Pose->GetElement(1,3) - controller1Pose->GetElement(1,3)) + * (controller0Pose->GetElement(1,3) - controller1Pose->GetElement(1,3)) + + (controller0Pose->GetElement(2,3) - controller1Pose->GetElement(2,3)) + * (controller0Pose->GetElement(2,3) - controller1Pose->GetElement(2,3)) ); + + // X axis is the displacement vector from controller 0 to 1 + double xAxis[3] = { + controller1Pose->GetElement(0,3) - controller0Pose->GetElement(0,3), + controller1Pose->GetElement(1,3) - controller0Pose->GetElement(1,3), + controller1Pose->GetElement(2,3) - controller0Pose->GetElement(2,3) }; + vtkMath::Normalize(xAxis); + + // Y axis is calculated from a Y' and Z. + // 1. Y' is the average orientation of the two controller directions + // 2. If X and Y' are almost parallel, then return failure + // 3. Z is the cross product of X and Y' + // 4. Y is then the cross product of Z and X + double yAxisPrime[3] = { + controller0Pose->GetElement(0,1) + controller1Pose->GetElement(0,1), + controller0Pose->GetElement(1,1) + controller1Pose->GetElement(1,1), + controller0Pose->GetElement(2,1) + controller1Pose->GetElement(2,1) }; + vtkMath::Normalize(yAxisPrime); + + if (fabs(vtkMath::Dot(xAxis, yAxisPrime)) > 0.99) + { + // The two axes are almost parallel + return false; + } + + double zAxis[3] = {0.0}; + vtkMath::Cross(xAxis, yAxisPrime, zAxis); + vtkMath::Normalize(zAxis); + + double yAxis[3] = {0.0}; + vtkMath::Cross(zAxis, xAxis, yAxis); + vtkMath::Normalize(yAxis); + + // Assemble matrix from the axes, the scaling, and the position + for (int row=0;row<3;++row) + { + combinedPose->SetElement(row, 0, xAxis[row]*controllerDistance); + combinedPose->SetElement(row, 1, yAxis[row]*controllerDistance); + combinedPose->SetElement(row, 2, zAxis[row]*controllerDistance); + combinedPose->SetElement(row, 3, controllerCenterPos[row]); + } + + return true; +} + +//--------------------------------------------------------------------------- +void vtkSlicerVirtualRealityLogic::SetTriggerButtonFunction(vtkVRRenderWindowInteractor* rwi, const std::string& functionId) +{ + vtkVRInteractorStyle* vrInteractorStyle = vtkVRInteractorStyle::SafeDownCast(rwi->GetInteractorStyle()); + if (!vrInteractorStyle) + { + vtkWarningWithObjectMacro(rwi, "SetTriggerButtonFunction: Current interactor style is not a VR interactor style"); + return; + } + + // The "eventId to state" mapping (see call to `MapInputToAction` below) applies to right and left + // controller buttons because they are bound to the same eventId: + // - `vtk_openvr_binding_*.json` files define the "button to action" mapping + // - `vtkOpenVRInteractorStyle()` contructor defines the "action to eventId" mapping + + if (functionId.empty()) + { + vrInteractorStyle->MapInputToAction(vtkCommand::Select3DEvent, VTKIS_NONE); + } + else if (!functionId.compare(vtkSlicerVirtualRealityLogic::GetButtonFunctionIdForGrabObjectsAndWorld())) + { + vrInteractorStyle->MapInputToAction(vtkCommand::Select3DEvent, VTKIS_POSITION_PROP); + } + else + { + vtkErrorWithObjectMacro(rwi, "SetTriggerButtonFunction: Unknown function identifier '" << functionId << "'"); + } +} + +//---------------------------------------------------------------------------- +void vtkSlicerVirtualRealityLogic::SetGestureButtonToTrigger(vtkVRRenderWindowInteractor* rwi) +{ + vtkVRInteractorStyle* vrInteractorStyle = vtkVRInteractorStyle::SafeDownCast(rwi->GetInteractorStyle()); + if (!vrInteractorStyle) + { + vtkWarningWithObjectMacro(rwi, "SetGestureButtonToTrigger: Current interactor style is not a VR interactor style"); + return; + } + + // The update "action to (eventId|function)" mapping (see call to `AddAction` below) applies to + // right and left controller buttons because they are bound to the same eventId: + // - "vtk_openvr_binding_*.json" defines the "button -> action" mapping + // - vtkOpenVRInteractorStyle defines the "action -> eventId" mapping + rwi->AddAction("/actions/vtk/in/TriggerAction", /*isAnalog=*/false, + [rwi](vtkEventData* ed) { rwi->HandleComplexGestureEvents(ed); }); + rwi->AddAction("/actions/vtk/in/ComplexGestureAction", vtkCommand::Select3DEvent, /*isAnalog=*/false); +} + +//---------------------------------------------------------------------------- +void vtkSlicerVirtualRealityLogic::SetGestureButtonToGrip(vtkVRRenderWindowInteractor* rwi) +{ + vtkVRInteractorStyle* vrInteractorStyle = vtkVRInteractorStyle::SafeDownCast(rwi->GetInteractorStyle()); + if (!vrInteractorStyle) + { + vtkWarningWithObjectMacro(rwi, "SetGestureButtonToGrip: Current interactor style is not a VR interactor style"); + return; + } + + // The update "action to (eventId|function)" mapping (see call to `AddAction` below) applies to + // right and left controller buttons because they are bound to the same eventId: + // - "vtk_openvr_binding_*.json" defines the "button -> action" mapping + // - vtkOpenVRInteractorStyle defines the "action -> eventId" mapping + rwi->AddAction("/actions/vtk/in/TriggerAction", vtkCommand::Select3DEvent, /*isAnalog=*/false); + rwi->AddAction("/actions/vtk/in/ComplexGestureAction", /*isAnalog=*/false, + [rwi](vtkEventData* ed) { rwi->HandleComplexGestureEvents(ed); }); +} + +//---------------------------------------------------------------------------- +void vtkSlicerVirtualRealityLogic::SetGestureButtonToTriggerAndGrip(vtkVRRenderWindowInteractor* rwi) +{ + vtkVRInteractorStyle* vrInteractorStyle = vtkVRInteractorStyle::SafeDownCast(rwi->GetInteractorStyle()); + if (!vrInteractorStyle) + { + vtkWarningWithObjectMacro(rwi, "SetGestureButtonToTriggerAndGrip: Current interactor style is not a VR interactor style"); + return; + } + + // The update "action to (eventId|function)" mapping (see call to `AddAction` below) applies to + // right and left controller buttons because they are bound to the same eventId: + // - "vtk_openvr_binding_*.json" defines the "button -> action" mapping + // - vtkOpenVRInteractorStyle defines the "action -> eventId" mapping + rwi->AddAction("/actions/vtk/in/TriggerAction", /*isAnalog=*/false, + [rwi](vtkEventData* ed) { rwi->HandleComplexGestureEvents(ed); }); + rwi->AddAction("/actions/vtk/in/ComplexGestureAction", /*isAnalog=*/false, + [rwi](vtkEventData* ed) { rwi->HandleComplexGestureEvents(ed); }); +} + +//---------------------------------------------------------------------------- +void vtkSlicerVirtualRealityLogic::SetGestureButtonToNone(vtkVRRenderWindowInteractor* rwi) +{ + vtkVRInteractorStyle* vrInteractorStyle = vtkVRInteractorStyle::SafeDownCast(rwi->GetInteractorStyle()); + if (!vrInteractorStyle) + { + vtkWarningWithObjectMacro(rwi, "SetGestureButtonToNone: Current interactor style is not a VR interactor style"); + return; + } + + // The update "action to (eventId|function)" mapping (see call to `AddAction` below) applies to + // right and left controller buttons because they are bound to the same eventId: + // - "vtk_openvr_binding_*.json" defines the "button -> action" mapping + // - vtkOpenVRInteractorStyle defines the "action -> eventId" mapping + rwi->AddAction("/actions/vtk/in/TriggerAction", /*isAnalog=*/false, + [](vtkEventData* vtkNotUsed(ed)) { }); + rwi->AddAction("/actions/vtk/in/ComplexGestureAction", /*isAnalog=*/false, + [](vtkEventData* vtkNotUsed(ed)) { }); +} + +//----------------------------------------------------------------------------- +std::string vtkSlicerVirtualRealityLogic::ComputeActionManifestPath(vtkMRMLVirtualRealityViewNode::XRRuntimeType xrRuntime) +{ + std::string actionManifestPath = + Self::ComputeActionManifestPath(this->GetModuleShareDirectory(), xrRuntime, this->ModuleInstalled); + + std::string actionManifestCollapsedPath = vtksys::SystemTools::CollapseFullPath(actionManifestPath); + + if (!vtksys::SystemTools::FileExists(actionManifestCollapsedPath)) + { + vtkErrorMacro(<< "ComputeActionManifestPath: Action manifest path set for " + << vtkMRMLVirtualRealityViewNode::GetXRRuntimeAsString(xrRuntime) << " runtime does not exist\n" + << " actionManifestPath: " << actionManifestPath + << " actionManifestCollapsedPath: " << actionManifestCollapsedPath); + } + + std::string expectedActionManifestFileName; + + switch (xrRuntime) + { + case vtkMRMLVirtualRealityViewNode::OpenVR: + expectedActionManifestFileName = "vtk_openvr_actions.json"; + break; + case vtkMRMLVirtualRealityViewNode::OpenXR: + expectedActionManifestFileName = "vtk_openxr_actions.json"; + break; + default: + vtkErrorWithObjectMacro(nullptr, << "ComputeActionManifestPath: Failed to set the expected action manifest filename for " + << vtkMRMLVirtualRealityViewNode::GetXRRuntimeAsString(xrRuntime) << " runtime"); + break; + } + + if (!expectedActionManifestFileName.empty()) + { + std::string manifestFullPath = actionManifestCollapsedPath + "/" + expectedActionManifestFileName; + if (!vtksys::SystemTools::FileExists(manifestFullPath)) + { + vtkErrorMacro(<< "ComputeActionManifestPath: Action manifest not found for " + << vtkMRMLVirtualRealityViewNode::GetXRRuntimeAsString(xrRuntime) << " runtime: " + << manifestFullPath); + } + } + + return actionManifestCollapsedPath + "/"; +} + +//----------------------------------------------------------------------------- +std::string vtkSlicerVirtualRealityLogic::ComputeActionManifestPath( + const std::string& moduleShareDirectory, vtkMRMLVirtualRealityViewNode::XRRuntimeType xrRuntime, bool installed) +{ + std::string actionManifestPath; + + if(installed) + { + // Since the output of vtkSlicerModuleLogic::GetModuleShareDirectory() is + // + // //share/Slicer-X.Y/qt-loadable-modules/ + // + // and the action manifest files are in this directory + // + // /// + // + // where + // + // is the "*_actions" sub-directory hard-coded in "VTK/Rendering/(OpenVR|OpenXR)/CMakeLists.txt" + // + // corresponds to the Slicer_THIRDPARTY_LIB_DIR macro (e.g "lib/Slicer-X.Y") which + // corresponding CMake option is used in "External_vtkRenderingOpen(VR|XR).cmake" + // + // we compose the path as such: + + // First, we retrieve + + // ... then we change the directory to /// + switch (xrRuntime) + { + case vtkMRMLVirtualRealityViewNode::OpenVR: + actionManifestPath = moduleShareDirectory + "/../../../../" Slicer_THIRDPARTY_LIB_DIR "/vr_actions/"; + break; + case vtkMRMLVirtualRealityViewNode::OpenXR: + actionManifestPath = moduleShareDirectory + "/../../../../" Slicer_THIRDPARTY_LIB_DIR "/xr_actions/"; + break; + default: + vtkErrorWithObjectMacro(nullptr, << "ComputeActionManifestPath: No install tree action manifest path set for" + << vtkMRMLVirtualRealityViewNode::GetXRRuntimeAsString(xrRuntime) << " runtime"); + break; + } + } + else + { + // Since the output of vtkSlicerModuleLogic::GetModuleShareDirectory() is + // + // /inner-build/share/Slicer-X.Y/qt-loadable-modules/ + // + // and the action manifest files are in this directory + // + // /-build// + // + // we compose the path as such: + + // First, we retrieve + + // ... then we change the directory to -build// + switch (xrRuntime) + { + case vtkMRMLVirtualRealityViewNode::OpenVR: + actionManifestPath = moduleShareDirectory + "/../../../../../vtkRenderingOpenVR-build/vtkRenderingOpenVR/"; + break; + case vtkMRMLVirtualRealityViewNode::OpenXR: + actionManifestPath = moduleShareDirectory + "/../../../../../vtkRenderingOpenXR-build/vtkRenderingOpenXR/"; + break; + default: + vtkErrorWithObjectMacro(nullptr, <<"ComputeActionManifestPath: No build tree action manifest path set for" + << vtkMRMLVirtualRealityViewNode::GetXRRuntimeAsString(xrRuntime) << " runtime"); + break; + } + } + + return actionManifestPath; +} diff --git a/VirtualReality/Logic/vtkSlicerVirtualRealityLogic.h b/VirtualReality/Logic/vtkSlicerVirtualRealityLogic.h index e68244f..d4da43e 100644 --- a/VirtualReality/Logic/vtkSlicerVirtualRealityLogic.h +++ b/VirtualReality/Logic/vtkSlicerVirtualRealityLogic.h @@ -24,18 +24,25 @@ #ifndef __vtkSlicerVirtualRealityLogic_h #define __vtkSlicerVirtualRealityLogic_h +// VR Logic includes +#include "vtkSlicerVirtualRealityModuleLogicExport.h" + +// VR MRML includes +#include "vtkMRMLVirtualRealityViewNode.h" + // Slicer includes -#include "vtkSlicerModuleLogic.h" +#include +class vtkSlicerVolumeRenderingLogic; -// MRML includes +// VTK/Rendering/VR includes +class vtkVRRenderWindowInteractor; + +// VTK includes +class vtkMatrix4x4; // STD includes #include -#include "vtkSlicerVirtualRealityModuleLogicExport.h" - -class vtkSlicerVolumeRenderingLogic; -class vtkMRMLVirtualRealityViewNode; class VTK_SLICER_VIRTUALREALITY_MODULE_LOGIC_EXPORT vtkSlicerVirtualRealityLogic : public vtkSlicerModuleLogic @@ -43,6 +50,7 @@ class VTK_SLICER_VIRTUALREALITY_MODULE_LOGIC_EXPORT vtkSlicerVirtualRealityLogic public: static vtkSlicerVirtualRealityLogic* New(); vtkTypeMacro(vtkSlicerVirtualRealityLogic, vtkSlicerModuleLogic); + typedef vtkSlicerVirtualRealityLogic Self; void PrintSelf(ostream& os, vtkIndent indent) override; /// Creates a singleton virtual reality view node and adds it to the scene. @@ -57,10 +65,21 @@ class VTK_SLICER_VIRTUALREALITY_MODULE_LOGIC_EXPORT vtkSlicerVirtualRealityLogic /// Retrieves the default VR view node from the scene. Creates it if does not exist. vtkMRMLVirtualRealityViewNode* GetDefaultVirtualRealityViewNode(); + ///@{ + /// Set/get the XR runtime. + /// Adds virtual reality view node if not added yet. + /// \sa InitializeActiveViewNode() + void SetVirtualRealityXRRuntime(vtkMRMLVirtualRealityViewNode::XRRuntimeType id); + vtkMRMLVirtualRealityViewNode::XRRuntimeType GetVirtualRealityXRRuntime(); + ///}@ + + ///@{ /// Connect/disconnect to headset. /// Adds virtual reality view node if not added yet. + /// \sa InitializeActiveViewNode() void SetVirtualRealityConnected(bool connect); bool GetVirtualRealityConnected(); + ///}@ /// Enable rendering updates in headset. /// Connects to device if not yet connected. @@ -84,10 +103,91 @@ class VTK_SLICER_VIRTUALREALITY_MODULE_LOGIC_EXPORT vtkSlicerVirtualRealityLogic /// Set volume rendering logic void SetVolumeRenderingLogic(vtkSlicerVolumeRenderingLogic* volumeRenderingLogic); + /// Determines whether rendering should occur as quick view motion. + /// + /// This function evaluates the motion sensitivity and elapsed time to decide + /// whether rendering should be considered as quick view motion. It computes + /// limits based on the motion sensitivity and checks the change speed of + /// view position, direction, and up vector to determine if it qualifies as + /// quick view motion. + /// + /// \param motionSensitivity The sensitivity value (0.0 to 1.0) indicating the user's preference for motion. + /// \param physicalScale The physical scale of the rendering. + /// \param elapsedTimeInSec The time elapsed in seconds since the last view update. + /// \param lastViewPos The position in the last view. + /// \param lastViewDir The direction in the last view. + /// \param lastViewUp The up vector in the last view. + /// \param viewPos The current position. + /// \param viewDir The current direction. + /// \param viewUp The current up vector. + /// + /// \return True if rendering should be considered as quick view motion, false otherwise. + /// + /// \sa vtkMRMLVirtualRealityViewNode::MotionSensitivity() + /// \sa vtkVRRenderWindow::GetPhysicalScale() + static bool ShouldConsiderQuickViewMotion( + double motionSensitivity, double physicalScale, double elapsedTimeInSec, + double lastViewPos[3], double lastViewDir[3], double lastViewUp[3], + double viewPos[3], double viewDir[3], double viewUp[3]); + + /// Calculate the average pose of the two controllers for pinch 3D operations + /// + /// \return Success flag. Failure happens when the average orientation coincides + /// with the direction of the displacement of the two controllers + static bool CalculateCombinedControllerPose( + vtkMatrix4x4* controller0Pose, vtkMatrix4x4* controller1Pose, vtkMatrix4x4* combinedPose); + + /// Set trigger button function + /// By default it is the same as grab (\sa GetButtonFunctionIdForGrabObjectsAndWorld) + /// Empty string disables button + static void SetTriggerButtonFunction(vtkVRRenderWindowInteractor* rwi, const std::string& functionId); + + /// Get string constant corresponding to button function "grab objects and world" + static std::string GetButtonFunctionIdForGrabObjectsAndWorld() { return "GrabObjectsAndWorld"; }; + + ///@{ + /// Convenience functions to easily associate grab and world functions to one or more buttons. + /// When interaction with markups and other VTK MRML widgets will be implemented then we probably + /// will not need these low-level event mappings anymore, but in the short term it is an effective + /// workaround that enables prototyping of ideas. + static void SetGestureButtonToTrigger(vtkVRRenderWindowInteractor* rwi); + static void SetGestureButtonToGrip(vtkVRRenderWindowInteractor* rwi); + static void SetGestureButtonToTriggerAndGrip(vtkVRRenderWindowInteractor* rwi); + static void SetGestureButtonToNone(vtkVRRenderWindowInteractor* rwi); + ///@} + + ///@{ + /// Whether the module associated with this logic is loaded from an install tree or not. + vtkSetMacro(ModuleInstalled, bool); + vtkGetMacro(ModuleInstalled, bool); + ///@} + + ///@{ + /// Utility functions for computing the ActionManifestPath based on the module share directory, + /// XR runtime and module install state. + /// + /// \sa vtkVRRenderWindowInteractor::SetActionManifestDirectory() + std::string ComputeActionManifestPath(vtkMRMLVirtualRealityViewNode::XRRuntimeType xrRuntime); + static std::string ComputeActionManifestPath( + const std::string& moduleShareDirectory, vtkMRMLVirtualRealityViewNode::XRRuntimeType xrRuntime, bool installed); + ///@} + protected: vtkSlicerVirtualRealityLogic(); ~vtkSlicerVirtualRealityLogic() override; + /// Initialize the active Virtual Reality view node. + /// + /// Checks if there is already an active Virtual Reality view node. + /// If not, it looks for an existing node in the MRML scene. + /// If no node is found, a new Virtual Reality view node is added to + /// the scene and set as the active node. + /// If the creation or retrieval fails, an error message is logged. + /// + /// The active view node is set internally for future use and can + /// be obtained using GetActiveViewNode(). + void InitializeActiveViewNode(); + void SetActiveViewNode(vtkMRMLVirtualRealityViewNode* vrViewNode); void SetMRMLSceneInternal(vtkMRMLScene* newScene) override; @@ -106,6 +206,8 @@ class VTK_SLICER_VIRTUALREALITY_MODULE_LOGIC_EXPORT vtkSlicerVirtualRealityLogic /// Volume rendering logic vtkSlicerVolumeRenderingLogic* VolumeRenderingLogic; + bool ModuleInstalled{false}; + private: vtkSlicerVirtualRealityLogic(const vtkSlicerVirtualRealityLogic&); // Not implemented void operator=(const vtkSlicerVirtualRealityLogic&); // Not implemented diff --git a/VirtualReality/MRML/CMakeLists.txt b/VirtualReality/MRML/CMakeLists.txt index e93f7e7..9f367f4 100644 --- a/VirtualReality/MRML/CMakeLists.txt +++ b/VirtualReality/MRML/CMakeLists.txt @@ -2,6 +2,12 @@ project(vtkSlicer${MODULE_NAME}ModuleMRML) set(KIT ${PROJECT_NAME}) +#----------------------------------------------------------------------------- +configure_file( + ${CMAKE_CURRENT_SOURCE_DIR}/vtkMRMLVirtualRealityConfigure.h.in + ${CMAKE_CURRENT_BINARY_DIR}/vtkMRMLVirtualRealityConfigure.h + ) + #----------------------------------------------------------------------------- set(${KIT}_EXPORT_DIRECTIVE "VTK_SLICER_${MODULE_NAME_UPPER}_MODULE_MRML_EXPORT") @@ -19,8 +25,18 @@ set(${KIT}_SRCS set(${KIT}_TARGET_LIBRARIES ${VTK_LIBRARIES} ${MRML_LIBRARIES} - VTK::RenderingOpenVR + VTK::RenderingVR ) +if(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + list(APPEND ${KIT}_TARGET_LIBRARIES + VTK::RenderingOpenVR + ) +endif() +if(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + list(APPEND ${KIT}_TARGET_LIBRARIES + VTK::RenderingOpenXR + ) +endif() #----------------------------------------------------------------------------- SlicerMacroBuildModuleMRML( diff --git a/VirtualReality/MRML/vtkMRMLVirtualRealityConfigure.h.in b/VirtualReality/MRML/vtkMRMLVirtualRealityConfigure.h.in new file mode 100644 index 0000000..3e41f7d --- /dev/null +++ b/VirtualReality/MRML/vtkMRMLVirtualRealityConfigure.h.in @@ -0,0 +1,7 @@ +#ifndef __vtkMRMLVirtualRealityConfigure_h +#define __vtkMRMLVirtualRealityConfigure_h + +#cmakedefine SlicerVirtualReality_HAS_OPENVR_SUPPORT +#cmakedefine SlicerVirtualReality_HAS_OPENXR_SUPPORT + +#endif diff --git a/VirtualReality/MRML/vtkMRMLVirtualRealityLayoutNode.cxx b/VirtualReality/MRML/vtkMRMLVirtualRealityLayoutNode.cxx index b0683a2..f95ebaf 100644 --- a/VirtualReality/MRML/vtkMRMLVirtualRealityLayoutNode.cxx +++ b/VirtualReality/MRML/vtkMRMLVirtualRealityLayoutNode.cxx @@ -18,16 +18,16 @@ ==============================================================================*/ -// STL includes -#include - // VTK includes #include #include -// MRML includes +// VR MRML includes #include "vtkMRMLVirtualRealityLayoutNode.h" +// STD includes +#include + //---------------------------------------------------------------------------- vtkMRMLNodeNewMacro(vtkMRMLVirtualRealityLayoutNode); diff --git a/VirtualReality/MRML/vtkMRMLVirtualRealityLayoutNode.h b/VirtualReality/MRML/vtkMRMLVirtualRealityLayoutNode.h index f1b7931..193286a 100644 --- a/VirtualReality/MRML/vtkMRMLVirtualRealityLayoutNode.h +++ b/VirtualReality/MRML/vtkMRMLVirtualRealityLayoutNode.h @@ -24,6 +24,7 @@ // MRML includes #include "vtkMRMLAbstractLayoutNode.h" +// VR MRML includes #include "vtkSlicerVirtualRealityModuleMRMLExport.h" /// \brief Node that describes the virtual reality layout of the application. diff --git a/VirtualReality/MRML/vtkMRMLVirtualRealityViewNode.cxx b/VirtualReality/MRML/vtkMRMLVirtualRealityViewNode.cxx index 20d1968..49dfcc3 100644 --- a/VirtualReality/MRML/vtkMRMLVirtualRealityViewNode.cxx +++ b/VirtualReality/MRML/vtkMRMLVirtualRealityViewNode.cxx @@ -13,8 +13,10 @@ Version: $Revision: 1.3 $ =========================================================================auto=*/ // MRML includes -#include "vtkMRMLScene.h" -#include "vtkMRMLViewNode.h" +#include +#include + +// VR MRML includes #include "vtkMRMLVirtualRealityViewNode.h" // VTK includes @@ -70,6 +72,7 @@ void vtkMRMLVirtualRealityViewNode::WriteXML(ostream& of, int nIndent) this->Superclass::WriteXML(of, nIndent); vtkMRMLWriteXMLBeginMacro(of); + vtkMRMLWriteXMLEnumMacro(xrRuntimeInteface, XRRuntime); vtkMRMLWriteXMLBooleanMacro(twoSidedLighting, TwoSidedLighting); vtkMRMLWriteXMLBooleanMacro(backLights, BackLights); vtkMRMLWriteXMLFloatMacro(desiredUpdateRate, DesiredUpdateRate); @@ -91,6 +94,7 @@ void vtkMRMLVirtualRealityViewNode::ReadXMLAttributes(const char** atts) this->Superclass::ReadXMLAttributes(atts); vtkMRMLReadXMLBeginMacro(atts); + vtkMRMLReadXMLEnumMacro(xrRuntime, XRRuntime); vtkMRMLReadXMLBooleanMacro(twoSidedLighting, TwoSidedLighting); vtkMRMLReadXMLBooleanMacro(backLights, BackLights); vtkMRMLReadXMLFloatMacro(desiredUpdateRate, DesiredUpdateRate); @@ -116,6 +120,7 @@ void vtkMRMLVirtualRealityViewNode::Copy(vtkMRMLNode* anode) this->Superclass::Copy(anode); vtkMRMLCopyBeginMacro(anode); + vtkMRMLCopyEnumMacro(XRRuntime); vtkMRMLCopyBooleanMacro(TwoSidedLighting); vtkMRMLCopyBooleanMacro(BackLights); vtkMRMLCopyFloatMacro(DesiredUpdateRate); @@ -137,6 +142,7 @@ void vtkMRMLVirtualRealityViewNode::PrintSelf(ostream& os, vtkIndent indent) this->Superclass::PrintSelf(os, indent); vtkMRMLPrintBeginMacro(os, indent); + vtkMRMLPrintEnumMacro(XRRuntime); vtkMRMLPrintBooleanMacro(TwoSidedLighting); vtkMRMLPrintBooleanMacro(BackLights); vtkMRMLPrintFloatMacro(DesiredUpdateRate); @@ -600,6 +606,46 @@ void vtkMRMLVirtualRealityViewNode::SetTrackerTransformUpdate(bool enable) this->Modified(); } +//----------------------------------------------------------- +void vtkMRMLVirtualRealityViewNode::SetXRRuntime(int id) +{ + this->SetXRRuntime(static_cast(id)); +} + +//----------------------------------------------------------- +const char* vtkMRMLVirtualRealityViewNode::GetXRRuntimeAsString(int id) +{ + switch (id) + { + case UndefinedXRRuntime: return "undefined"; + case OpenVR: return "OpenVR"; + case OpenXR: return "OpenXR"; + default: + // invalid id + return ""; + } +} + +//----------------------------------------------------------- +int vtkMRMLVirtualRealityViewNode::GetXRRuntimeFromString(const char* name) +{ + if (name == nullptr) + { + // invalid name + return -1; + } + for (int ii = 0; ii < XRRuntime_Last; ii++) + { + if (strcmp(name, vtkMRMLVirtualRealityViewNode::GetXRRuntimeAsString(ii)) == 0) + { + // found a matching name + return ii; + } + } + // unknown name + return -1; +} + //---------------------------------------------------------------------------- bool vtkMRMLVirtualRealityViewNode::HasError() { diff --git a/VirtualReality/MRML/vtkMRMLVirtualRealityViewNode.h b/VirtualReality/MRML/vtkMRMLVirtualRealityViewNode.h index c7c9b09..f55a1d5 100644 --- a/VirtualReality/MRML/vtkMRMLVirtualRealityViewNode.h +++ b/VirtualReality/MRML/vtkMRMLVirtualRealityViewNode.h @@ -15,6 +15,11 @@ #ifndef __vtkMRMLVirtualRealityViewNode_h #define __vtkMRMLVirtualRealityViewNode_h +// For: +// - SlicerVirtualReality_HAS_OPENVR_SUPPORT +// - SlicerVirtualReality_HAS_OPENXR_SUPPORT +#include "vtkMRMLVirtualRealityConfigure.h" + // MRML includes #include #include @@ -22,6 +27,7 @@ // VTK includes #include +// VR MRML includes #include "vtkSlicerVirtualRealityModuleMRMLExport.h" /// \brief MRML node to represent a 3D view. @@ -37,6 +43,14 @@ class VTK_SLICER_VIRTUALREALITY_MODULE_MRML_EXPORT vtkMRMLVirtualRealityViewNode vtkTypeMacro(vtkMRMLVirtualRealityViewNode, vtkMRMLViewNode); void PrintSelf(ostream& os, vtkIndent indent) override; + enum XRRuntimeType : int + { + UndefinedXRRuntime, + OpenVR, + OpenXR, + XRRuntime_Last // must be last + }; + //-------------------------------------------------------------------------- /// MRMLNode methods //-------------------------------------------------------------------------- @@ -213,6 +227,26 @@ class VTK_SLICER_VIRTUALREALITY_MODULE_MRML_EXPORT vtkMRMLVirtualRealityViewNode vtkBooleanMacro(LighthouseModelsVisible, bool); ///}@ + ///@{ + /// Get/Set the XR runtime interface. + vtkGetMacro(XRRuntime, XRRuntimeType); + vtkSetMacro(XRRuntime, XRRuntimeType); + ///@} + +#ifndef __WRAP__ + /// Set the XR runtime interface. + /// + /// Excluded from wrapping to avoid the following error: + /// `TypeError: "ambuguous call, multiple overloaded methods match the arguments` + void SetXRRuntime(int id); +#endif + + ///@{ + /// Convert between XR Runtime Interface identifier and name + static const char* GetXRRuntimeAsString(int id); + static int GetXRRuntimeFromString(const char* name); + ///@} + /// Return true if an error has occurred. /// "Connected" member requests connection but this method can tell if the /// hardware connection has been actually successfully established. @@ -228,6 +262,14 @@ class VTK_SLICER_VIRTUALREALITY_MODULE_MRML_EXPORT vtkMRMLVirtualRealityViewNode std::string GetError() const; protected: +#if defined(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + XRRuntimeType XRRuntime{vtkMRMLVirtualRealityViewNode::OpenVR}; +#elif defined(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + XRRuntimeType XRRuntime{vtkMRMLVirtualRealityViewNode::OpenXR}; +#else + XRRuntimeType XRRuntime{vtkMRMLVirtualRealityViewNode::UndefinedXRRuntime}; +#endif + bool TwoSidedLighting; bool BackLights; double DesiredUpdateRate; diff --git a/VirtualReality/MRMLDM/CMakeLists.txt b/VirtualReality/MRMLDM/CMakeLists.txt index 60db6ea..f660abb 100644 --- a/VirtualReality/MRMLDM/CMakeLists.txt +++ b/VirtualReality/MRMLDM/CMakeLists.txt @@ -17,13 +17,29 @@ set(${KIT}_INCLUDE_DIRECTORIES set(${KIT}_SRCS vtkMRML${MODULE_NAME}ViewDisplayableManagerFactory.cxx vtkMRML${MODULE_NAME}ViewDisplayableManagerFactory.h - vtk${MODULE_NAME}ViewInteractor.cxx - vtk${MODULE_NAME}ViewInteractor.h + vtk${MODULE_NAME}ComplexGestureRecognizer.cxx + vtk${MODULE_NAME}ComplexGestureRecognizer.h vtk${MODULE_NAME}ViewInteractorObserver.cxx vtk${MODULE_NAME}ViewInteractorObserver.h - vtk${MODULE_NAME}ViewInteractorStyle.cxx - vtk${MODULE_NAME}ViewInteractorStyle.h + vtk${MODULE_NAME}ViewInteractorStyleDelegate.cxx + vtk${MODULE_NAME}ViewInteractorStyleDelegate.h ) +if(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + list(APPEND ${KIT}_SRCS + vtk${MODULE_NAME}ViewOpenVRInteractor.cxx + vtk${MODULE_NAME}ViewOpenVRInteractor.h + vtk${MODULE_NAME}ViewOpenVRInteractorStyle.cxx + vtk${MODULE_NAME}ViewOpenVRInteractorStyle.h + ) +endif() +if(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + list(APPEND ${KIT}_SRCS + vtk${MODULE_NAME}ViewOpenXRInteractor.cxx + vtk${MODULE_NAME}ViewOpenXRInteractor.h + vtk${MODULE_NAME}ViewOpenXRInteractorStyle.cxx + vtk${MODULE_NAME}ViewOpenXRInteractorStyle.h + ) +endif() set(${KIT}_TARGET_LIBRARIES ${MRML_LIBRARIES} diff --git a/VirtualReality/MRMLDM/vtkMRMLVirtualRealityViewDisplayableManagerFactory.cxx b/VirtualReality/MRMLDM/vtkMRMLVirtualRealityViewDisplayableManagerFactory.cxx index e71895f..12374c3 100644 --- a/VirtualReality/MRMLDM/vtkMRMLVirtualRealityViewDisplayableManagerFactory.cxx +++ b/VirtualReality/MRMLDM/vtkMRMLVirtualRealityViewDisplayableManagerFactory.cxx @@ -18,7 +18,7 @@ ==============================================================================*/ -// MRMLDisplayableManager includes +// VR MRMLDM includes #include "vtkMRMLVirtualRealityViewDisplayableManagerFactory.h" // VTK includes diff --git a/VirtualReality/MRMLDM/vtkMRMLVirtualRealityViewDisplayableManagerFactory.h b/VirtualReality/MRMLDM/vtkMRMLVirtualRealityViewDisplayableManagerFactory.h index a3b113f..6e0ccfa 100644 --- a/VirtualReality/MRMLDM/vtkMRMLVirtualRealityViewDisplayableManagerFactory.h +++ b/VirtualReality/MRMLDM/vtkMRMLVirtualRealityViewDisplayableManagerFactory.h @@ -21,7 +21,7 @@ #ifndef __vtkMRMLVirtualRealityViewDisplayableManagerFactory_h #define __vtkMRMLVirtualRealityViewDisplayableManagerFactory_h -// VirtualRealityModule includes +// VR MRMLDM includes #include "vtkSlicerVirtualRealityModuleMRMLDisplayableManagerExport.h" // MRMLDisplayableManager includes diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityComplexGestureRecognizer.cxx b/VirtualReality/MRMLDM/vtkVirtualRealityComplexGestureRecognizer.cxx new file mode 100644 index 0000000..1b2d58f --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityComplexGestureRecognizer.cxx @@ -0,0 +1,168 @@ +/*============================================================================== + + Copyright (c) Kitware Inc. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc. + and was supported through the University of Western Ontario. + +==============================================================================*/ + +// VR MRMLDM includes +#include "vtkVirtualRealityComplexGestureRecognizer.h" + +// VTK/Rendering/VR includes +#include +#include + +// VTK includes +#include +#include +#include +#include + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(vtkVirtualRealityComplexGestureRecognizer); + +//---------------------------------------------------------------------------- +void vtkVirtualRealityComplexGestureRecognizer::SetInteractor(vtkVRRenderWindowInteractor* interactor) +{ + vtkSetSmartPointerBodyMacro(Interactor, vtkVRRenderWindowInteractor, interactor); +} + +//---------------------------------------------------------------------------- +vtkVRRenderWindowInteractor* vtkVirtualRealityComplexGestureRecognizer::GetInteractor() const +{ + return this->Interactor; +} + +//------------------------------------------------------------------------------ +void vtkVirtualRealityComplexGestureRecognizer::HandleComplexGestureEvents(vtkEventData* ed) +{ + // + // SlicerVirtualReality implementation details: + // + // * The implementation of both HandleComplexGestureEvents() and RecognizeComplexGesture() + // was updated by removing the handling specific to Rotate and Pan to only keep + // he pinch event. + // + // * The update of the Scale was removed. + // + // * Update of "StartingPhysicalEventPoses" instead of "StartingPhysicalEventPositions": + // As originally described in SlicerVirtualReality@b6815f1cb, while the controllers + // move, the purpose is to have the PhysicalToWorld matrix modified so that the controller + // physical pose corresponds to the same rendering world pose. + // For reference, the "StartingPhysicalEventPoses" ivar was originally introduced in upstream + // VTK as VTK@803d3a327. + // + + vtkVRRenderWindowInteractor* rwi = this->Interactor; + if (rwi == nullptr) + { + vtkErrorMacro("HandleComplexGestureEvents failed: Interactor is not set"); + return; + } + + vtkEventDataDevice3D* edata = ed->GetAsEventDataDevice3D(); + if (edata == nullptr) + { + return; + } + + rwi->SetPointerIndex(static_cast(edata->GetDevice())); + int pointerIndex = rwi->GetPointerIndex(); + + if (edata->GetAction() == vtkEventDataAction::Press) + { + rwi->SetDeviceInputDownCount(edata->GetDevice(), 1); + + // StartingPhysicalEventPose + vtkNew physicalEventPose; + rwi->GetPhysicalEventPose(physicalEventPose, pointerIndex); + rwi->SetStartingPhysicalEventPose(physicalEventPose, edata->GetDevice()); + + // StartingPhysicalToWorldMatrix + vtkVRRenderWindow* renWin = vtkVRRenderWindow::SafeDownCast(rwi->GetRenderWindow()); + vtkNew physicalToWorldMatrix; + renWin->GetPhysicalToWorldMatrix(physicalToWorldMatrix); + rwi->SetStartingPhysicalToWorldMatrix(physicalToWorldMatrix); + + // Both controllers have the grip down, start multitouch + if (rwi->GetDeviceInputDownCount(vtkEventDataDevice::LeftController) && + rwi->GetDeviceInputDownCount(vtkEventDataDevice::RightController)) + { + // The gesture is still unknown + rwi->SetCurrentGesture(vtkCommand::StartEvent); + } + } + + // End the gesture if needed + if (edata->GetAction() == vtkEventDataAction::Release) + { + rwi->SetDeviceInputDownCount(edata->GetDevice(), 0); + + if (rwi->GetCurrentGesture() == vtkCommand::PinchEvent) + { + rwi->EndPinchEvent(); + vtkInteractorStyle* interactorStyle = vtkInteractorStyle::SafeDownCast(rwi->GetInteractorStyle()); + if (interactorStyle) + { + interactorStyle->EndGesture(); + } + } + rwi->SetCurrentGesture(vtkCommand::NoEvent); + + return; + } +} + +//---------------------------------------------------------------------------- +void vtkVirtualRealityComplexGestureRecognizer::RecognizeComplexGesture(vtkEventDataDevice3D* vtkNotUsed(edata)) +{ + vtkVRRenderWindowInteractor* rwi = this->Interactor; + if (rwi == nullptr) + { + vtkErrorMacro("RecognizeComplexGesture failed: Interactor is not set"); + return; + } + + // Recognize gesture only if one button is pressed per controller + vtkEventDataDevice lhand = vtkEventDataDevice::LeftController; + vtkEventDataDevice rhand = vtkEventDataDevice::RightController; + + if (rwi->GetDeviceInputDownCount(lhand) > 1 || rwi->GetDeviceInputDownCount(lhand) == 0 || + rwi->GetDeviceInputDownCount(rhand) > 1 || rwi->GetDeviceInputDownCount(rhand) == 0) + { + rwi->SetCurrentGesture(vtkCommand::NoEvent); + return; + } + + if (rwi->GetCurrentGesture() != vtkCommand::NoEvent) + { + if (rwi->GetCurrentGesture() == vtkCommand::StartEvent) + { + rwi->SetCurrentGesture(vtkCommand::PinchEvent); + + rwi->StartPinchEvent(); + vtkInteractorStyle* interactorStyle = vtkInteractorStyle::SafeDownCast(rwi->GetInteractorStyle()); + if (interactorStyle) + { + interactorStyle->StartGesture(); + } + } + + // If we have found a specific type of movement then handle it + if (rwi->GetCurrentGesture() == vtkCommand::PinchEvent) + { + rwi->PinchEvent(); + } + } +} diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityComplexGestureRecognizer.h b/VirtualReality/MRMLDM/vtkVirtualRealityComplexGestureRecognizer.h new file mode 100644 index 0000000..7626db7 --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityComplexGestureRecognizer.h @@ -0,0 +1,66 @@ +/*============================================================================== + + Copyright (c) Kitware Inc. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc. + and was supported through the University of Western Ontario. + +==============================================================================*/ + +#ifndef vtkVirtualRealityComplexGestureRecognizer_h +#define vtkVirtualRealityComplexGestureRecognizer_h + +// VR MRMLDM includes +#include "vtkSlicerVirtualRealityModuleMRMLDisplayableManagerExport.h" + +// VTK/Rendering/VR includes +class vtkVRRenderWindowInteractor; + +// VTK includes +#include +#include +class vtkEventData; +class vtkEventDataDevice3D; + + +class VTK_SLICER_VIRTUALREALITY_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkVirtualRealityComplexGestureRecognizer + : public vtkObject +{ +public: + static vtkVirtualRealityComplexGestureRecognizer *New(); + typedef vtkVirtualRealityComplexGestureRecognizer Self; + vtkTypeMacro(vtkVirtualRealityComplexGestureRecognizer,vtkObject); + + ///@{ + /// Interactor to recognize the complex gestures from. + void SetInteractor(vtkVRRenderWindowInteractor* interactor); + vtkVRRenderWindowInteractor* GetInteractor() const; + ///}@ + + ///@{ + /// Define Slicer specific heuristic for handling complex gestures. + void HandleComplexGestureEvents(vtkEventData* ed); + void RecognizeComplexGesture(vtkEventDataDevice3D* edata); + ///@} + +protected: + vtkSmartPointer Interactor; + +private: + vtkVirtualRealityComplexGestureRecognizer() = default; + ~vtkVirtualRealityComplexGestureRecognizer() override = default; + + vtkVirtualRealityComplexGestureRecognizer(const vtkVirtualRealityComplexGestureRecognizer&) = delete; + void operator=(const vtkVirtualRealityComplexGestureRecognizer&) = delete; +}; + +#endif diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractor.cxx b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractor.cxx deleted file mode 100644 index 0063bb3..0000000 --- a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractor.cxx +++ /dev/null @@ -1,323 +0,0 @@ -/*============================================================================== - - Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) - Queen's University, Kingston, ON, Canada. All Rights Reserved. - - See COPYRIGHT.txt - or http://www.slicer.org/copyright/copyright.txt for details. - - 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. - - This file was originally developed by Csaba Pinter, PerkLab, Queen's University - and was supported through CANARIE's Research Software Program, and Cancer - Care Ontario. - -==============================================================================*/ - -#include "vtkVirtualRealityViewInteractor.h" - -// VTK includes -#include "vtkMatrix4x4.h" -#include "vtkObjectFactory.h" -#include "vtkVRRenderWindow.h" - -// SlicerVR includes -#include "vtkVirtualRealityViewInteractorStyle.h" - -vtkStandardNewMacro(vtkVirtualRealityViewInteractor); - -//---------------------------------------------------------------------------- -vtkVirtualRealityViewInteractor::vtkVirtualRealityViewInteractor() -{ - this->GestureEnabledButtons.push_back(static_cast(vtkEventDataDeviceInput::Grip)); -} - -//---------------------------------------------------------------------------- -vtkVirtualRealityViewInteractor::~vtkVirtualRealityViewInteractor() -{ -} - -//------------------------------------------------------------------------------ -vtkCommand::EventIds vtkVirtualRealityViewInteractor::GetCurrentGesture() const -{ - return this->CurrentGesture; -} - -//------------------------------------------------------------------------------ -void vtkVirtualRealityViewInteractor::HandleComplexGestureEvents(vtkEventData* ed) -{ - // [SlicerVirtualReality] - // - // SlicerVirtualReality implementation details: - // - // * The implementation of both HandleGripEvents() and RecognizeComplexGesture() - // was updated by removing the handling specific to Rotate and Pan to only keep - // he pinch event. - // - // * The update of the Scale was removed. - // - // * The lines either enclosed in `[SlicerVirtualReality]` or associated with a - // `// SlicerVirtualReality` comment are specific to this extension. - // - // [/SlicerVirtualReality] - - vtkEventDataDevice3D* edata = ed->GetAsEventDataDevice3D(); - if (!edata) - { - return; - } - - this->PointerIndex = static_cast(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]; - - // [SlicerVirtualReality] - // As originally described in SlicerVirtualReality@b6815f1cb, while the controllers - // move, the purpose is to have the PhysicalToWorld matrix modified so that the controller - // physical pose corresponds to the same rendering world pose. - // - // For reference, the "StartingPhysicalEventPoses" ivar was originally introduced in upstream - // VTK as VTK@803d3a327. - this->StartingPhysicalEventPoses[this->PointerIndex]->DeepCopy( - this->PhysicalEventPoses[this->PointerIndex]); - // [/SlicerVirtualReality] - - vtkVRRenderWindow* renWin = vtkVRRenderWindow::SafeDownCast(this->RenderWindow); - renWin->GetPhysicalToWorldMatrix(this->StartingPhysicalToWorldMatrix); - - // Both controllers have the grip down, start multitouch - if (this->DeviceInputDownCount[static_cast(vtkEventDataDevice::LeftController)] && - this->DeviceInputDownCount[static_cast(vtkEventDataDevice::RightController)]) - { - // The gesture is still unknown - this->CurrentGesture = vtkCommand::StartEvent; - } - } - - // End the gesture if needed - if (edata->GetAction() == vtkEventDataAction::Release) - { - this->DeviceInputDownCount[this->PointerIndex] = 0; - - if (this->CurrentGesture == vtkCommand::PinchEvent) - { - this->EndPinchEvent(); - vtkInteractorStyle* interactorStyle = vtkInteractorStyle::SafeDownCast(this->InteractorStyle); - if (interactorStyle) - { - interactorStyle->EndGesture(); - } - } - this->CurrentGesture = vtkCommand::NoEvent; - - return; - } -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractor::RecognizeComplexGesture(vtkEventDataDevice3D* vtkNotUsed(edata)) -{ - // Recognize gesture only if one button is pressed per controller - int lhand = static_cast(vtkEventDataDevice::LeftController); - int rhand = static_cast(vtkEventDataDevice::RightController); - - if (this->DeviceInputDownCount[lhand] > 1 || this->DeviceInputDownCount[lhand] == 0 || - this->DeviceInputDownCount[rhand] > 1 || this->DeviceInputDownCount[rhand] == 0) - { - this->CurrentGesture = vtkCommand::NoEvent; - return; - } - - // [SlicerVirtualReality] - // 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]; - // [/SlicerVirtualReality] - - if (this->CurrentGesture != vtkCommand::NoEvent) - { - - // [SlicerVirtualReality] - // Calculate the distances - // double originalDistance = sqrt(vtkMath::Distance2BetweenPoints(startVals[0], startVals[1])); - // double newDistance = sqrt(vtkMath::Distance2BetweenPoints(posVals[0], posVals[1])); - // [/SlicerVirtualReality] - - if (this->CurrentGesture == vtkCommand::StartEvent) - { - this->CurrentGesture = vtkCommand::PinchEvent; - // this->Scale = 1.0; // SlicerVirtualReality - this->StartPinchEvent(); - vtkInteractorStyle* interactorStyle = vtkInteractorStyle::SafeDownCast(this->InteractorStyle); - if (interactorStyle) - { - interactorStyle->StartGesture(); - } - } - - // If we have found a specific type of movement then handle it - if (this->CurrentGesture == vtkCommand::PinchEvent) - { - // this->SetScale(newDistance / originalDistance); // SlicerVirtualReality - this->PinchEvent(); - } - } -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractor::PrintSelf(ostream& os, vtkIndent indent) -{ - this->Superclass::PrintSelf(os,indent); - os << indent << "StartedMessageLoop: " << this->StartedMessageLoop << endl; -} - -//---------------------------------------------------------------------- -void vtkVirtualRealityViewInteractor::SetInteractorStyle(vtkInteractorObserver* style) -{ - Superclass::SetInteractorStyle(style); - - // Set default trigger button function "grab objects and world" - this->SetTriggerButtonFunction(vtkVirtualRealityViewInteractor::GetButtonFunctionIdForGrabObjectsAndWorld()); -} - -//--------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractor::SetTriggerButtonFunction(std::string functionId) -{ - vtkVirtualRealityViewInteractorStyle* vrInteractorStyle = vtkVirtualRealityViewInteractorStyle::SafeDownCast(this->InteractorStyle); - if (!vrInteractorStyle) - { - vtkWarningMacro("SetTriggerButtonFunction: Current interactor style is not a VR interactor style"); - return; - } - - // The "eventId to state" mapping (see call to `MapInputToAction` below) applies to right and left - // controller buttons because they are bound to the same eventId: - // - `vtk_openvr_binding_*.json` files define the "button to action" mapping - // - `vtkOpenVRInteractorStyle()` contructor defines the "action to eventId" mapping - - if (functionId.empty()) - { - vrInteractorStyle->MapInputToAction(vtkCommand::Select3DEvent, VTKIS_NONE); - - this->GestureEnabledButtons.clear(); - this->GestureEnabledButtons.push_back(static_cast(vtkEventDataDeviceInput::Grip)); - } - else if (!functionId.compare(vtkVirtualRealityViewInteractor::GetButtonFunctionIdForGrabObjectsAndWorld())) - { - vrInteractorStyle->MapInputToAction(vtkCommand::Select3DEvent, VTKIS_POSITION_PROP); - - this->GestureEnabledButtons.clear(); - this->GestureEnabledButtons.push_back(static_cast(vtkEventDataDeviceInput::Grip)); - this->GestureEnabledButtons.push_back(static_cast(vtkEventDataDeviceInput::Trigger)); - } - else - { - vtkErrorMacro("SetTriggerButtonFunction: Unknown function identifier '" << functionId << "'"); - } -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractor::SetGestureButtonToTrigger() -{ - vtkVirtualRealityViewInteractorStyle* vrInteractorStyle = vtkVirtualRealityViewInteractorStyle::SafeDownCast(this->InteractorStyle); - if (!vrInteractorStyle) - { - vtkWarningMacro("SetGestureButtonToTrigger: Current interactor style is not a VR interactor style"); - return; - } - - // The update "action to (eventId|function)" mapping (see call to `AddAction` below) applies to - // right and left controller buttons because they are bound to the same eventId: - // - "vtk_openvr_binding_*.json" defines the "button -> action" mapping - // - vtkOpenVRInteractorStyle defines the "action -> eventId" mapping - this->AddAction("/actions/vtk/in/TriggerAction", /*isAnalog=*/false, - [this](vtkEventData* ed) { this->HandleComplexGestureEvents(ed); }); - this->AddAction("/actions/vtk/in/ComplexGestureAction", vtkCommand::Select3DEvent, /*isAnalog=*/false); - - this->GestureEnabledButtons.clear(); - this->GestureEnabledButtons.push_back(static_cast(vtkEventDataDeviceInput::Trigger)); -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractor::SetGestureButtonToGrip() -{ - vtkVirtualRealityViewInteractorStyle* vrInteractorStyle = vtkVirtualRealityViewInteractorStyle::SafeDownCast(this->InteractorStyle); - if (!vrInteractorStyle) - { - vtkWarningMacro("SetGestureButtonToGrip: Current interactor style is not a VR interactor style"); - return; - } - - // The update "action to (eventId|function)" mapping (see call to `AddAction` below) applies to - // right and left controller buttons because they are bound to the same eventId: - // - "vtk_openvr_binding_*.json" defines the "button -> action" mapping - // - vtkOpenVRInteractorStyle defines the "action -> eventId" mapping - this->AddAction("/actions/vtk/in/TriggerAction", vtkCommand::Select3DEvent, /*isAnalog=*/false); - this->AddAction("/actions/vtk/in/ComplexGestureAction", /*isAnalog=*/false, - [this](vtkEventData* ed) { this->HandleComplexGestureEvents(ed); }); - - this->GestureEnabledButtons.clear(); - this->GestureEnabledButtons.push_back(static_cast(vtkEventDataDeviceInput::Grip)); -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractor::SetGestureButtonToTriggerAndGrip() -{ - vtkVirtualRealityViewInteractorStyle* vrInteractorStyle = vtkVirtualRealityViewInteractorStyle::SafeDownCast(this->InteractorStyle); - if (!vrInteractorStyle) - { - vtkWarningMacro("SetGestureButtonToTriggerAndGrip: Current interactor style is not a VR interactor style"); - return; - } - - // The update "action to (eventId|function)" mapping (see call to `AddAction` below) applies to - // right and left controller buttons because they are bound to the same eventId: - // - "vtk_openvr_binding_*.json" defines the "button -> action" mapping - // - vtkOpenVRInteractorStyle defines the "action -> eventId" mapping - this->AddAction("/actions/vtk/in/TriggerAction", /*isAnalog=*/false, - [this](vtkEventData* ed) { this->HandleComplexGestureEvents(ed); }); - this->AddAction("/actions/vtk/in/ComplexGestureAction", /*isAnalog=*/false, - [this](vtkEventData* ed) { this->HandleComplexGestureEvents(ed); }); - - this->GestureEnabledButtons.clear(); - this->GestureEnabledButtons.push_back(static_cast(vtkEventDataDeviceInput::Grip)); - this->GestureEnabledButtons.push_back(static_cast(vtkEventDataDeviceInput::Trigger)); -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractor::SetGestureButtonToNone() -{ - vtkVirtualRealityViewInteractorStyle* vrInteractorStyle = vtkVirtualRealityViewInteractorStyle::SafeDownCast(this->InteractorStyle); - if (!vrInteractorStyle) - { - vtkWarningMacro("SetGestureButtonToNone: Current interactor style is not a VR interactor style"); - return; - } - - // The update "action to (eventId|function)" mapping (see call to `AddAction` below) applies to - // right and left controller buttons because they are bound to the same eventId: - // - "vtk_openvr_binding_*.json" defines the "button -> action" mapping - // - vtkOpenVRInteractorStyle defines the "action -> eventId" mapping - this->AddAction("/actions/vtk/in/TriggerAction", /*isAnalog=*/false, - [](vtkEventData* vtkNotUsed(ed)) { }); - this->AddAction("/actions/vtk/in/ComplexGestureAction", /*isAnalog=*/false, - [](vtkEventData* vtkNotUsed(ed)) { }); - - this->GestureEnabledButtons.clear(); -} diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractor.h b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractor.h deleted file mode 100644 index cace858..0000000 --- a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractor.h +++ /dev/null @@ -1,97 +0,0 @@ -/*============================================================================== - - Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) - Queen's University, Kingston, ON, Canada. All Rights Reserved. - - See COPYRIGHT.txt - or http://www.slicer.org/copyright/copyright.txt for details. - - 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. - - This file was originally developed by Csaba Pinter, PerkLab, Queen's University - and was supported through CANARIE's Research Software Program, and Cancer - Care Ontario. - -==============================================================================*/ - -#ifndef vtkVirtualRealityViewInteractor_h -#define vtkVirtualRealityViewInteractor_h - -// VirtualRealityModule includes -#include "vtkSlicerVirtualRealityModuleMRMLDisplayableManagerExport.h" - -// VTK includes -#include "vtkOpenVRRenderWindowInteractor.h" - -// STD includes -#include - -// vtkRenderingOpenVR is not python wrapped, so wrapping New causes linking error //TODO: -#ifndef __VTK_WRAP__ - -class VTK_SLICER_VIRTUALREALITY_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkVirtualRealityViewInteractor - : public vtkOpenVRRenderWindowInteractor -{ -public: - static vtkVirtualRealityViewInteractor *New(); - - typedef vtkVirtualRealityViewInteractor Self; - - vtkTypeMacro(vtkVirtualRealityViewInteractor,vtkOpenVRRenderWindowInteractor); - void PrintSelf(ostream& os, vtkIndent indent) override; - - virtual void SetInteractorStyle(vtkInteractorObserver*) override; - - /// Return the identifier of the complex gesture being handled. - /// \sa HandleComplexGestureEvents(), RecognizeComplexGesture() - /// \sa vtkVirtualRealityViewInteractorStyle::OnStartGesture() - /// \sa vtkVirtualRealityViewInteractorStyle::OnEndGesture() - vtkCommand::EventIds GetCurrentGesture() const; - - ///@{ - /// Define Slicer specific heuristic for handling complex gestures. - /// - /// See https://gitlab.kitware.com/vtk/vtk/-/merge_requests/9892 - virtual void HandleComplexGestureEvents(vtkEventData* ed) override; - virtual void RecognizeComplexGesture(vtkEventDataDevice3D* edata) override; - ///@} - - /// Set trigger button function - /// By default it is the same as grab (\sa GetButtonFunctionIdForGrabObjectsAndWorld) - /// Empty string disables button - void SetTriggerButtonFunction(std::string functionId); - - /// Get string constant corresponding to button function "grab objects and world" - static std::string GetButtonFunctionIdForGrabObjectsAndWorld() { return "GrabObjectsAndWorld"; }; - - ///@{ - /// Convenience functions to easily associate grab and world functions to one or more buttons. - /// When interaction with markups and other VTK MRML widgets will be implemented then we probably - /// will not need these low-level event mappings anymore, but in the short term it is an effective - /// workaround that enables prototyping of ideas. - void SetGestureButtonToTrigger(); - void SetGestureButtonToGrip(); - void SetGestureButtonToTriggerAndGrip(); - void SetGestureButtonToNone(); - ///@} - -protected: - - /// List of buttons for which gesture recognition is enabled - std::vector GestureEnabledButtons; - -private: - vtkVirtualRealityViewInteractor(); - ~vtkVirtualRealityViewInteractor() override; - - vtkVirtualRealityViewInteractor(const vtkVirtualRealityViewInteractor&) = delete; - void operator=(const vtkVirtualRealityViewInteractor&) = delete; -}; - -#endif // __VTK_WRAP__ - -#endif diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorObserver.cxx b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorObserver.cxx index f3bbd82..55155e4 100644 --- a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorObserver.cxx +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorObserver.cxx @@ -18,13 +18,26 @@ #include "vtkVirtualRealityViewInteractorObserver.h" -// SlicerVirtualReality includes -#include "vtkVirtualRealityViewInteractor.h" -#include "vtkVirtualRealityViewInteractorStyle.h" +// For: +// - SlicerVirtualReality_HAS_OPENVR_SUPPORT +// - SlicerVirtualReality_HAS_OPENXR_SUPPORT +#include "vtkMRMLVirtualRealityConfigure.h" + +// VR MRMLDM includes +#include "vtkVirtualRealityViewInteractorStyleDelegate.h" +#if defined(SlicerVirtualReality_HAS_OPENVR_SUPPORT) +#include "vtkVirtualRealityViewOpenVRInteractorStyle.h" +#endif +#if defined(SlicerVirtualReality_HAS_OPENXR_SUPPORT) +#include "vtkVirtualRealityViewOpenXRInteractorStyle.h" +#endif // MRML includes #include // For MRML_APPLICATION_VERSION and MRML_VERSION_CHECK -#include "vtkMRMLInteractionEventData.h" +#include + +// VTK Rendering/VR includes +#include // VTK includes #include @@ -208,20 +221,21 @@ bool vtkVirtualRealityViewInteractorObserver::DelegateInteractionEventToDisplaya //---------------------------------------------------------------------------- bool vtkVirtualRealityViewInteractorObserver::DelegateInteractionEventDataToDisplayableManagers(vtkMRMLInteractionEventData* ed) { - - vtkVirtualRealityViewInteractorStyle* vrViewInteractorStyle = - vtkVirtualRealityViewInteractorStyle::SafeDownCast(this->GetInteractorStyle()); - if (vrViewInteractorStyle) + vtkVirtualRealityViewInteractorStyleDelegate* delegate = this->GetInteractorStyleDelegate(); + if (delegate != nullptr) { - ed->SetWorldToPhysicalScale(vrViewInteractorStyle->GetMagnification()); - ed->SetAccuratePicker(vrViewInteractorStyle->GetAccuratePicker()); + ed->SetWorldToPhysicalScale(delegate->GetMagnification()); + // The following line is commented out because the "AccuratePicker" associated with + // the eventData (ed) is exclusively used in "Libs/MRML/DisplayableManager/vtkMRMLCameraWidget.cxx" + // that is irrelevant in the VR context. + // ed->SetAccuratePicker(delegate->GetAccuratePicker()); } vtkRenderer* currentRenderer = this->GetInteractorStyle()->GetCurrentRenderer(); ed->SetRenderer(currentRenderer); - vtkVirtualRealityViewInteractor* vrViewInteractor = - vtkVirtualRealityViewInteractor::SafeDownCast(this->GetInteractor()); + vtkVRRenderWindowInteractor* vrViewInteractor = + vtkVRRenderWindowInteractor::SafeDownCast(this->GetInteractor()); std::string interactionContextName; if (ed->GetDevice() == vtkEventDataDevice::LeftController) @@ -293,3 +307,24 @@ void vtkVirtualRealityViewInteractorObserver::OnElevation3D(vtkEventData *edata) { this->GetInteractorStyle()->OnElevation3D(edata); } + +//------------------------------------------------------------------------------ +vtkVirtualRealityViewInteractorStyleDelegate* vtkVirtualRealityViewInteractorObserver::GetInteractorStyleDelegate() +{ + vtkVirtualRealityViewInteractorStyleDelegate* delegate = nullptr; + +#if defined(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + if (delegate == nullptr) + { + vtkVirtualRealityViewOpenVRInteractorStyle* vrViewInteractorStyle = + vtkVirtualRealityViewOpenVRInteractorStyle::SafeDownCast(this->GetInteractorStyle()); + if (vrViewInteractorStyle == nullptr) + { + return nullptr; + } + delegate = vrViewInteractorStyle->GetInteractorStyleDelegate(); + } +#endif + + return delegate; +} diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorObserver.h b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorObserver.h index 7c6eb57..4bf39fd 100644 --- a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorObserver.h +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorObserver.h @@ -19,11 +19,12 @@ #ifndef __vtkVirtualRealityViewInteractorObserver_h #define __vtkVirtualRealityViewInteractorObserver_h -// VirtualRealityModule includes +// VR MRMLDM includes #include "vtkSlicerVirtualRealityModuleMRMLDisplayableManagerExport.h" +class vtkVirtualRealityViewInteractorStyleDelegate; // MRML includes -#include "vtkMRMLViewInteractorStyle.h" +#include class VTK_SLICER_VIRTUALREALITY_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkVirtualRealityViewInteractorObserver : public vtkMRMLViewInteractorStyle @@ -78,6 +79,8 @@ class VTK_SLICER_VIRTUALREALITY_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkVirtualR virtual void OnClip3D(vtkEventData *edata); virtual void OnElevation3D(vtkEventData *edata); + vtkVirtualRealityViewInteractorStyleDelegate* GetInteractorStyleDelegate(); + protected: vtkVirtualRealityViewInteractorObserver(); ~vtkVirtualRealityViewInteractorObserver() override; @@ -85,9 +88,6 @@ class VTK_SLICER_VIRTUALREALITY_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkVirtualR private: vtkVirtualRealityViewInteractorObserver(const vtkVirtualRealityViewInteractorObserver&) = delete; void operator=(const vtkVirtualRealityViewInteractorObserver&) = delete; - - class vtkInternal; - vtkInternal* Internal; }; #endif diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyle.cxx b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyle.cxx deleted file mode 100644 index ee0388b..0000000 --- a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyle.cxx +++ /dev/null @@ -1,722 +0,0 @@ -/*============================================================================== - - Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) - Queen's University, Kingston, ON, Canada. All Rights Reserved. - - See COPYRIGHT.txt - or http://www.slicer.org/copyright/copyright.txt for details. - - 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. - - This file was originally developed by Csaba Pinter, PerkLab, Queen's University - and was supported through CANARIE's Research Software Program, and Cancer - Care Ontario. - -==============================================================================*/ - -#include "vtkVirtualRealityViewInteractorStyle.h" - -// VR MRML includes -#include "vtkMRMLVirtualRealityViewNode.h" - -// MRML includes -#include "vtkMRMLAbstractThreeDViewDisplayableManager.h" -#include "vtkMRMLDisplayableManagerGroup.h" -#include "vtkMRMLDisplayableNode.h" -#include "vtkMRMLDisplayNode.h" -#include "vtkMRMLInteractionEventData.h" -#include "vtkMRMLLinearTransformNode.h" -#include "vtkMRMLScene.h" - -// VTK includes -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include "vtkOpenVRRenderWindow.h" -#include "vtkOpenVRRenderWindowInteractor.h" - - -//---------------------------------------------------------------------------- -vtkStandardNewMacro(vtkVirtualRealityViewInteractorStyle); - -//---------------------------------------------------------------------------- -class vtkVirtualRealityViewInteractorStyle::vtkInternal -{ -public: - vtkInternal(); - ~vtkInternal(); - - /// Calculate the average pose of the two controllers for pinch 3D operations - /// \return Success flag. Failure happens when the average orientation coincides - /// with the direction of the displacement of the two controllers - bool CalculateCombinedControllerPose( - vtkMatrix4x4* controller0Pose, vtkMatrix4x4* controller1Pose, vtkMatrix4x4* combinedPose); - -public: - vtkWeakPointer PickedNode[vtkEventDataNumberOfDevices]; - - //vtkPlane* ClippingPlanes[vtkEventDataNumberOfDevices]; - - vtkNew CombinedStartingControllerPose; - bool StartingControllerPoseValid; - vtkNew LastValidCombinedControllerPose; - bool LastValidCombinedControllerPoseExists; -}; - -//--------------------------------------------------------------------------- -// vtkInternal methods - -//--------------------------------------------------------------------------- -vtkVirtualRealityViewInteractorStyle::vtkInternal::vtkInternal() -{ - for (int d = 0; d < vtkEventDataNumberOfDevices; ++d) - { - this->PickedNode[d] = nullptr; - //this->ClippingPlanes[d] = nullptr; - } -} - -//--------------------------------------------------------------------------- -vtkVirtualRealityViewInteractorStyle::vtkInternal::~vtkInternal() -{ -} - -//--------------------------------------------------------------------------- -bool vtkVirtualRealityViewInteractorStyle::vtkInternal::CalculateCombinedControllerPose( - vtkMatrix4x4* controller0Pose, vtkMatrix4x4* controller1Pose, vtkMatrix4x4* combinedPose ) -{ - if (!controller0Pose || !controller1Pose || !combinedPose) - { - return false; - } - - // The position will be the average position - double controllerCenterPos[3] = { - (controller0Pose->GetElement(0,3) + controller1Pose->GetElement(0,3)) / 2.0, - (controller0Pose->GetElement(1,3) + controller1Pose->GetElement(1,3)) / 2.0, - (controller0Pose->GetElement(2,3) + controller1Pose->GetElement(2,3)) / 2.0 }; - - // Scaling will be the distance between the two controllers - double controllerDistance = sqrt( - (controller0Pose->GetElement(0,3) - controller1Pose->GetElement(0,3)) - * (controller0Pose->GetElement(0,3) - controller1Pose->GetElement(0,3)) + - (controller0Pose->GetElement(1,3) - controller1Pose->GetElement(1,3)) - * (controller0Pose->GetElement(1,3) - controller1Pose->GetElement(1,3)) + - (controller0Pose->GetElement(2,3) - controller1Pose->GetElement(2,3)) - * (controller0Pose->GetElement(2,3) - controller1Pose->GetElement(2,3)) ); - - // X axis is the displacement vector from controller 0 to 1 - double xAxis[3] = { - controller1Pose->GetElement(0,3) - controller0Pose->GetElement(0,3), - controller1Pose->GetElement(1,3) - controller0Pose->GetElement(1,3), - controller1Pose->GetElement(2,3) - controller0Pose->GetElement(2,3) }; - vtkMath::Normalize(xAxis); - - // Y axis is calculated from a Y' and Z. - // 1. Y' is the average orientation of the two controller directions - // 2. If X and Y' are almost parallel, then return failure - // 3. Z is the cross product of X and Y' - // 4. Y is then the cross product of Z and X - double yAxisPrime[3] = { - controller0Pose->GetElement(0,1) + controller1Pose->GetElement(0,1), - controller0Pose->GetElement(1,1) + controller1Pose->GetElement(1,1), - controller0Pose->GetElement(2,1) + controller1Pose->GetElement(2,1) }; - vtkMath::Normalize(yAxisPrime); - - if (fabs(vtkMath::Dot(xAxis, yAxisPrime)) > 0.99) - { - // The two axes are almost parallel - return false; - } - - double zAxis[3] = {0.0}; - vtkMath::Cross(xAxis, yAxisPrime, zAxis); - vtkMath::Normalize(zAxis); - - double yAxis[3] = {0.0}; - vtkMath::Cross(zAxis, xAxis, yAxis); - vtkMath::Normalize(yAxis); - - // Assemble matrix from the axes, the scaling, and the position - for (int row=0;row<3;++row) - { - combinedPose->SetElement(row, 0, xAxis[row]*controllerDistance); - combinedPose->SetElement(row, 1, yAxis[row]*controllerDistance); - combinedPose->SetElement(row, 2, zAxis[row]*controllerDistance); - combinedPose->SetElement(row, 3, controllerCenterPos[row]); - } - - return true; -} - -//--------------------------------------------------------------------------- -// vtkVirtualRealityViewInteractorStyle methods - -//---------------------------------------------------------------------------- -vtkVirtualRealityViewInteractorStyle::vtkVirtualRealityViewInteractorStyle() -{ - this->GrabEnabled = 1; - - this->Internal = new vtkInternal(); - - - this->AccuratePicker = vtkSmartPointer::New(); - this->AccuratePicker->SetTolerance( .005 ); - this->QuickPicker = vtkSmartPointer::New(); - - // Create default inputs mapping - - // The "eventId to state" mapping (see call to `MapInputToAction` below) applies to right and left - // controller buttons because they are bound to the same eventId: - // - `vtk_openvr_binding_*.json` files define the "button to action" mapping - // - `vtkOpenVRInteractorStyle()` contructor defines the "action to eventId" mapping - - this->MapInputToAction(vtkCommand::ViewerMovement3DEvent, VTKIS_DOLLY); - this->MapInputToAction(vtkCommand::Select3DEvent, VTKIS_POSITION_PROP); - - /* - this->MapInputToAction(vtkEventDataDevice::RightController, - vtkEventDataDeviceInput::Grip, VTKIS_POSITION_PROP); - - this->MapInputToAction(vtkEventDataDevice::RightController, - vtkEventDataDeviceInput::TrackPad, VTKIS_DOLLY); - - this->MapInputToAction(vtkEventDataDevice::LeftController, - vtkEventDataDeviceInput::Grip, VTKIS_POSITION_PROP); - */ - - // Before in Slicer: - // vtkEventDataDevice::RightController, vtkEventDataDeviceInput::Grip -> VTKIS_POSITION_PROP - // - // After in Slicer: - // See above - // - // Before OpenVR refactoring to "action based input model" introduced in kitware/VTK@b7f02e622: - // vtkEventDataDevice::RightController vtkEventDataDeviceInput::Trigger -> VTKIS_POSITION_PROP - // - // After OpenVR refactoring: - // vtkCommand::Select3DEvent -> VTKIS_POSITION_PROP - - // Before in Slicer: - // vtkEventDataDevice::RightController, vtkEventDataDeviceInput::TrackPad -> VTKIS_DOLLY - // - // After in Slicer: - // See above - // - // Before OpenVR refactoring to "action based input model" introduced in kitware/VTK@b7f02e622: - // same mapping - // - // After OpenVR refactoring: - // Calling MapInputToAction with "vtkCommand::ViewerMovement3DEvent -> VTKIS_DOLLY" - - // Before in Slicer: - // vtkEventDataDevice::LeftController, vtkEventDataDeviceInput::Grip -> VTKIS_POSITION_PROP - // - // After in Slicer: - // See above - // - // Before OpenVR refactoring to "action based input model" introduced in kitware/VTK@b7f02e622: - // No mapping - // - // After OpenVR refactoring: - // No mapping -} - -//---------------------------------------------------------------------------- -vtkVirtualRealityViewInteractorStyle::~vtkVirtualRealityViewInteractorStyle() -{ - delete this->Internal; -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::PrintSelf(ostream& os, vtkIndent indent) -{ - this->Superclass::PrintSelf(os,indent); -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::SetDisplayableManagers(vtkMRMLDisplayableManagerGroup* displayableManagers) -{ - this->DisplayableManagers = displayableManagers; -} - -//---------------------------------------------------------------------------- -// Interaction methods -//---------------------------------------------------------------------------- - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::PositionProp(vtkEventData* ed, double* vtkNotUsed(lwpos), double* vtkNotUsed(lwori)) -{ - vtkEventDataDevice3D *edd = ed->GetAsEventDataDevice3D(); - if (!edd) - { - return; - } - int deviceIndex = static_cast(edd->GetDevice()); - vtkMRMLDisplayableNode* pickedNode = this->Internal->PickedNode[deviceIndex]; - - if ( pickedNode == nullptr || !pickedNode->GetSelectable() - || !this->CurrentRenderer || !this->DisplayableManagers ) - { - return; - } - if (ed->GetType() != vtkCommand::Move3DEvent) - { - return; - } - - // Get positions and orientations - vtkRenderWindowInteractor3D* rwi = static_cast(this->Interactor); - double worldPos[3] = {0.0}; - edd->GetWorldPosition(worldPos); - double* lastWorldPos = rwi->GetLastWorldEventPosition(rwi->GetPointerIndex()); - double* worldOrientation = rwi->GetWorldEventOrientation(rwi->GetPointerIndex()); - double* lastWorldOrientation = rwi->GetLastWorldEventOrientation(rwi->GetPointerIndex()); - - // Calculate transform - vtkNew interactionTransform; - interactionTransform->PreMultiply(); - - double translation[3] = {0.0}; - for (int i = 0; i < 3; i++) - { - translation[i] = worldPos[i] - lastWorldPos[i]; - } - vtkQuaternion q1; - q1.SetRotationAngleAndAxis(vtkMath::RadiansFromDegrees( - lastWorldOrientation[0]), lastWorldOrientation[1], lastWorldOrientation[2], lastWorldOrientation[3]); - vtkQuaternion q2; - q2.SetRotationAngleAndAxis(vtkMath::RadiansFromDegrees( - worldOrientation[0]), worldOrientation[1], worldOrientation[2], worldOrientation[3]); - q1.Conjugate(); - q2 = q2*q1; - double axis[4] = {0.0}; - axis[0] = vtkMath::DegreesFromRadians(q2.GetRotationAngleAndAxis(axis+1)); - interactionTransform->Translate(worldPos[0], worldPos[1], worldPos[2]); - interactionTransform->RotateWXYZ(axis[0], axis[1], axis[2], axis[3]); - interactionTransform->Translate(-worldPos[0], -worldPos[1], -worldPos[2]); - interactionTransform->Translate(translation[0], translation[1], translation[2]); - - // Make sure that the topmost parent transform is the VR interaction transform - vtkMRMLTransformNode* topTransformNode = pickedNode->GetParentTransformNode(); - while (topTransformNode && topTransformNode->GetParentTransformNode()) - { - topTransformNode = topTransformNode->GetParentTransformNode(); - } - vtkMRMLTransformNode* vrTransformNode = nullptr; - if (!topTransformNode) - { - // Create interaction transform if no transform found - vtkNew newTransformNode; - std::string vrTransformNodeName(pickedNode->GetName()); - vrTransformNodeName.append("_VR_Interaction_Transform"); - newTransformNode->SetName(vrTransformNodeName.c_str()); - newTransformNode->SetAttribute( - vtkMRMLVirtualRealityViewNode::GetVirtualRealityInteractionTransformAttributeName(), "1"); - pickedNode->GetScene()->AddNode(newTransformNode); - pickedNode->SetAndObserveTransformNodeID(newTransformNode->GetID()); - vrTransformNode = newTransformNode.GetPointer(); - } - else - { - // Make top transform the VR interaction transform - if (!topTransformNode->GetAttribute( - vtkMRMLVirtualRealityViewNode::GetVirtualRealityInteractionTransformAttributeName()) ) - { - vtkInfoMacro("PositionProp: Transform " << topTransformNode->GetName() << - " is now used as VR interaction transform for node " << pickedNode->GetName()); - topTransformNode->SetAttribute( - vtkMRMLVirtualRealityViewNode::GetVirtualRealityInteractionTransformAttributeName(), "1"); - } - // VR interaction transform found (i.e. the top transform has the VR interaction transform attribute) - vrTransformNode = topTransformNode; - } - - // Apply interaction transform - vtkTransform* lastVrInteractionTransform = vtkTransform::SafeDownCast(vrTransformNode->GetTransformToParent()); - if (vrTransformNode->GetTransformToParent() && !lastVrInteractionTransform) - { - //TODO: Remove constraint - vtkErrorMacro("PositionProp: Node " << pickedNode->GetName() << " contains non-linear transform"); - return; - } - // Use the interaction transform with the flattened matrix to prevent concatenation - // of potentially thousands of transforms - if (lastVrInteractionTransform) - { - interactionTransform->Concatenate(lastVrInteractionTransform->GetMatrix()); - lastVrInteractionTransform->DeepCopy(interactionTransform); - } - else - { - vrTransformNode->SetAndObserveTransformToParent(interactionTransform); - } - - if (this->AutoAdjustCameraClippingRange) - { - this->CurrentRenderer->ResetCameraClippingRange(); - } -} - -//---------------------------------------------------------------------------- -// Interaction entry points -//---------------------------------------------------------------------------- - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::StartPositionProp(vtkEventDataDevice3D* edata) -{ - vtkMRMLScene* scene = this->GetMRMLScene(); - if (!scene) - { - return; - } - - int deviceIndex = static_cast(edata->GetDevice()); - - double pos[3] = {0.0}; - edata->GetWorldPosition(pos); - - // Get MRML node to move - for (int i=0; iDisplayableManagers->GetDisplayableManagerCount(); ++i) - { - vtkMRMLAbstractThreeDViewDisplayableManager* currentDisplayableManager = - vtkMRMLAbstractThreeDViewDisplayableManager::SafeDownCast(this->DisplayableManagers->GetNthDisplayableManager(i)); - if (!currentDisplayableManager) - { - continue; - } - currentDisplayableManager->Pick3D(pos); - vtkMRMLDisplayNode* displayNode = vtkMRMLDisplayNode::SafeDownCast( - scene->GetNodeByID(currentDisplayableManager->GetPickedNodeID()) ); - if (!displayNode) - { - continue; - } - //TODO: Only the first selectable picked node in the last displayable manager will be picked - vtkMRMLDisplayableNode* pickedNode = displayNode->GetDisplayableNode(); - if (pickedNode && pickedNode->GetSelectable() && this->GrabEnabled) - { - this->Internal->PickedNode[deviceIndex] = pickedNode; - } - } - - this->InteractionState[deviceIndex] = VTKIS_POSITION_PROP; - - // Don't start action if a controller is already positioning the prop - int rc = static_cast(vtkEventDataDevice::RightController); - int lc = static_cast(vtkEventDataDevice::LeftController); - if (this->Internal->PickedNode[rc] == this->Internal->PickedNode[lc]) - { - this->EndPositionProp(edata); - return; - } - - if (this->CurrentRenderer == nullptr || this->InteractionProp == nullptr) - { - return; - } -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::EndPositionProp(vtkEventDataDevice3D* edata) -{ - int deviceIndex = static_cast(edata->GetDevice()); - this->InteractionState[deviceIndex] = VTKIS_NONE; - this->Internal->PickedNode[deviceIndex] = nullptr; -} - -//---------------------------------------------------------------------------- -// Multitouch interaction methods -//---------------------------------------------------------------------------- - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::OnPan() -{ - this->OnPinch3D(); -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::OnPinch() -{ - this->OnPinch3D(); -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::OnRotate() -{ - this->OnPinch3D(); -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::OnPinch3D() -{ - int rc = static_cast(vtkEventDataDevice::RightController); - int lc = static_cast(vtkEventDataDevice::LeftController); - if ( (this->Internal->PickedNode[rc] && this->Internal->PickedNode[rc]->GetSelectable()) - || (this->Internal->PickedNode[lc] && this->Internal->PickedNode[lc]->GetSelectable()) ) - { - // If a node is being picked then don't do the pinch 3D operation - return; - } - - this->InteractionState[rc] = VTKIS_ZOOM; - this->InteractionState[lc] = VTKIS_ZOOM; - - if (this->CurrentRenderer == nullptr) - { - return; - } - if (!this->Internal->StartingControllerPoseValid) - { - // If starting pose is not valid then cannot do the pinch 3D operation - return; - } - - vtkOpenVRRenderWindowInteractor* rwi = static_cast(this->Interactor); - vtkOpenVRRenderWindow* rw = static_cast(rwi->GetRenderWindow()); - - int pointer = this->Interactor->GetPointerIndex(); - - this->FindPokedRenderer( - this->Interactor->GetEventPositions(pointer)[0], this->Interactor->GetEventPositions(pointer)[1]); - - // Get current controller poses - vtkMatrix4x4* currentController0Pose_Physical = - rw->GetDeviceToPhysicalMatrixForDevice(vtkEventDataDevice::LeftController); - vtkMatrix4x4* currentController1Pose_Physical = - rw->GetDeviceToPhysicalMatrixForDevice(vtkEventDataDevice::RightController); - - // Get combined current controller pose - vtkNew combinedCurrentControllerPose; - if ( !this->Internal->CalculateCombinedControllerPose( - currentController0Pose_Physical, currentController1Pose_Physical, combinedCurrentControllerPose ) ) - { - // If combined pose is invalid, then use the last valid pose if it exists - if (this->Internal->LastValidCombinedControllerPoseExists) - { - combinedCurrentControllerPose->DeepCopy(this->Internal->LastValidCombinedControllerPose); - } - else - { - // There is no valid last position so cannot do the pinch 3D operation - return; - } - } - else - { - // Save current as last valid pose - this->Internal->LastValidCombinedControllerPose->DeepCopy(combinedCurrentControllerPose); - } - - // Calculate modifier matrix - vtkNew inverseCombinedCurrentControllerPose; - vtkMatrix4x4::Invert(combinedCurrentControllerPose, inverseCombinedCurrentControllerPose); - - vtkNew modifyPhysicalToWorldMatrix; - vtkMatrix4x4::Multiply4x4( - this->Internal->CombinedStartingControllerPose, inverseCombinedCurrentControllerPose, modifyPhysicalToWorldMatrix); - - // Calculate new physical to world matrix - vtkNew startingPhysicalToWorldMatrix; - rwi->GetStartingPhysicalToWorldMatrix(startingPhysicalToWorldMatrix); - vtkNew newPhysicalToWorldMatrix; - vtkMatrix4x4::Multiply4x4( - startingPhysicalToWorldMatrix, modifyPhysicalToWorldMatrix, newPhysicalToWorldMatrix); - - // Set new physical to world matrix - rw->SetPhysicalToWorldMatrix(newPhysicalToWorldMatrix); - if (this->AutoAdjustCameraClippingRange) - { - this->CurrentRenderer->ResetCameraClippingRange(); - } - if (this->Interactor->GetLightFollowCamera()) - { - this->CurrentRenderer->UpdateLightsGeometryToFollowCamera(); - } -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::StartGesture() -{ - // Store combined starting controller pose - vtkOpenVRRenderWindowInteractor* rwi = static_cast(this->Interactor); - vtkOpenVRRenderWindow* rw = static_cast(rwi->GetRenderWindow()); - - vtkMatrix4x4* startingController0Pose_Physical = - rw->GetDeviceToPhysicalMatrixForDevice(vtkEventDataDevice::LeftController); - vtkMatrix4x4* startingController1Pose_Physical = - rw->GetDeviceToPhysicalMatrixForDevice(vtkEventDataDevice::RightController); - - if ( this->Internal->CalculateCombinedControllerPose( - startingController0Pose_Physical, startingController1Pose_Physical, this->Internal->CombinedStartingControllerPose) ) - { - this->Internal->StartingControllerPoseValid = true; - } -} - -//---------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::EndGesture() -{ - this->Internal->StartingControllerPoseValid = false; - this->Internal->LastValidCombinedControllerPoseExists = false; -} - -//---------------------------------------------------------------------------- -// Utility routines -//---------------------------------------------------------------------------- - -//---------------------------------------------------------------------------- -int vtkVirtualRealityViewInteractorStyle::GetMappedAction(vtkCommand::EventIds eid) -{ - // Since both `vtkEventDataAction::Press` and `vtkEventDataAction::Release` actions are - // added together to the map when using the 2-argument function - // `MapInputToAction(vtkCommand::EventIds eid, int state)`, and we are only using this - // version of the function, to get the state for the corresponding `(eventId, action)`, - // we simply lookup for `(eventId, vtkEventDataAction::Press)` to check if an event - // is already mapped. - vtkEventDataAction action = vtkEventDataAction::Press; - - decltype(this->InputMap)::key_type key(eid, action); - - auto it = this->InputMap.find(key); - if (it != this->InputMap.end()) - { - return it->second; - } - return VTKIS_NONE; -} - -//--------------------------------------------------------------------------- -vtkMRMLScene* vtkVirtualRealityViewInteractorStyle::GetMRMLScene() -{ - if (!this->DisplayableManagers - || this->DisplayableManagers->GetDisplayableManagerCount() == 0) - { - return nullptr; - } - - return this->DisplayableManagers->GetNthDisplayableManager(0)->GetMRMLScene(); -} - -//--------------------------------------------------------------------------- -vtkCellPicker* vtkVirtualRealityViewInteractorStyle::GetAccuratePicker() -{ - return this->AccuratePicker; -} - -//--------------------------------------------------------------------------- -void vtkVirtualRealityViewInteractorStyle::SetMagnification(double magnification) -{ - if (this->CurrentRenderer == nullptr) - { - return; - } - - vtkOpenVRRenderWindowInteractor* rwi = static_cast(this->Interactor); - vtkOpenVRRenderWindow* rw = static_cast(rwi->GetRenderWindow()); - - double currentPhysicalScale = rw->GetPhysicalScale(); - double newPhysicalScale = 1000.0 / magnification; - double scaleFactor = newPhysicalScale / currentPhysicalScale; - if (fabs(scaleFactor - 1.0) < 0.001) - { - return; - } - - // Get Physical to World_Origin matrix - vtkNew physicalToWorldOriginMatrix; - rw->GetPhysicalToWorldMatrix(physicalToWorldOriginMatrix); - - // Calculate World_Origin to World_FocusPoint matrix - double bounds[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; - this->CurrentRenderer->ComputeVisiblePropBounds(bounds); - double focusPoint_World[3] = { (bounds[1] + bounds[0]) / 2.0, - (bounds[3] + bounds[2]) / 2.0, - (bounds[5] + bounds[4]) / 2.0 }; - vtkNew worldOriginToWorldFocusPointMatrix; - for (int i=0; i<3; ++i) - { - worldOriginToWorldFocusPointMatrix->SetElement(i,3, -focusPoint_World[i]); - } - - // Calculate World_FocusPoint to World_ScaledFocusPoint matrix - vtkNew worldFocusPointToWorldScaledFocusPointMatrix; - for (int i=0; i<3; ++i) - { - worldFocusPointToWorldScaledFocusPointMatrix->SetElement(i,i, scaleFactor); - } - - // Calculate World_ScaledFocusPoint to World_ScaledOrigin matrix - vtkNew worldScaledFocusPointToWorldScaledOriginMatrix; - for (int i=0; i<3; ++i) - { - worldScaledFocusPointToWorldScaledOriginMatrix->SetElement(i,3, focusPoint_World[i]); - } - - // Calculate Physical to World_ScaledOrigin matrix - vtkNew worldFocusPointToWorldScaledOriginMatrix; - vtkMatrix4x4::Multiply4x4( worldScaledFocusPointToWorldScaledOriginMatrix, worldFocusPointToWorldScaledFocusPointMatrix, - worldFocusPointToWorldScaledOriginMatrix ); - - vtkNew worldOriginToWorldScaledOriginMatrix; - vtkMatrix4x4::Multiply4x4( worldFocusPointToWorldScaledOriginMatrix, worldOriginToWorldFocusPointMatrix, - worldOriginToWorldScaledOriginMatrix ); - - // Physical to World_ScaledOrigin - vtkNew physicalToWorldScaledOriginMatrix; - vtkMatrix4x4::Multiply4x4( worldOriginToWorldScaledOriginMatrix, physicalToWorldOriginMatrix, - physicalToWorldScaledOriginMatrix ); - - rw->SetPhysicalToWorldMatrix(physicalToWorldScaledOriginMatrix); - - if (this->AutoAdjustCameraClippingRange) - { - this->CurrentRenderer->ResetCameraClippingRange(); - } - if (this->Interactor->GetLightFollowCamera()) - { - this->CurrentRenderer->UpdateLightsGeometryToFollowCamera(); - } -} - -//--------------------------------------------------------------------------- -double vtkVirtualRealityViewInteractorStyle::GetMagnification() -{ - vtkOpenVRRenderWindowInteractor* rwi = static_cast(this->Interactor); - vtkOpenVRRenderWindow* rw = static_cast(rwi->GetRenderWindow()); - - return 1000.0 / rw->GetPhysicalScale(); -} - -//--------------------------------------------------------------------------- -bool vtkVirtualRealityViewInteractorStyle::QuickPick(int x, int y, double pickPoint[3]) -{ - this->FindPokedRenderer(x, y); - if (this->CurrentRenderer == nullptr) - { - vtkDebugMacro("Pick: couldn't find the poked renderer at event position " << x << ", " << y); - return false; - } - - this->QuickPicker->Pick(x, y, 0, this->CurrentRenderer); - - this->QuickPicker->GetPickPosition(pickPoint); - - return true; -} diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyle.h b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyle.h deleted file mode 100644 index e72ff18..0000000 --- a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyle.h +++ /dev/null @@ -1,131 +0,0 @@ -/*============================================================================== - - Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) - Queen's University, Kingston, ON, Canada. All Rights Reserved. - - See COPYRIGHT.txt - or http://www.slicer.org/copyright/copyright.txt for details. - - 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. - - This file was originally developed by Csaba Pinter, PerkLab, Queen's University - and was supported through CANARIE's Research Software Program, and Cancer - Care Ontario. - -==============================================================================*/ - -#ifndef __vtkVirtualRealityViewInteractorStyle_h -#define __vtkVirtualRealityViewInteractorStyle_h - -// VirtualRealityModule includes -#include "vtkSlicerVirtualRealityModuleMRMLDisplayableManagerExport.h" - -// MRML includes -#include "vtkMRMLViewInteractorStyle.h" - -// VTK includes -#include -#include -#include "vtkObject.h" -#include "vtkEventData.h" -#include - -class vtkMRMLScene; -class vtkMRMLDisplayableManagerGroup; -class vtkCellPicker; -class vtkWorldPointPicker; - -/// \brief Virtual reality interactions -/// -/// TODO: -/// -class VTK_SLICER_VIRTUALREALITY_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkVirtualRealityViewInteractorStyle - : public vtkOpenVRInteractorStyle -{ -public: - static vtkVirtualRealityViewInteractorStyle *New(); - vtkTypeMacro(vtkVirtualRealityViewInteractorStyle,vtkOpenVRInteractorStyle); - void PrintSelf(ostream& os, vtkIndent indent) override; - - void SetDisplayableManagers(vtkMRMLDisplayableManagerGroup* displayableManagers); - - /// Get MRML scene from the displayable manager group (the first displayable manager's if any) - vtkMRMLScene* GetMRMLScene(); - - //@{ - /** - * Interaction mode entry points. - */ - void StartPositionProp(vtkEventDataDevice3D *) override; - void EndPositionProp(vtkEventDataDevice3D *) override; - //@} - - //@{ - /** - * Multitouch events binding. - */ - void OnPan() override; - void OnPinch() override; - void OnRotate() override; - void OnPinch3D(); - void StartGesture() override; - void EndGesture() override; - //@} - - //@{ - /** - * Methods for interaction. - */ - void PositionProp(vtkEventData *, double* lwpos = nullptr, double* lwori = nullptr) override; - //@} - - //@{ - /** - * Map controller inputs to actions. - * Actions are defined by a VTKIS_*STATE*, interaction entry points, - * and the corresponding method for interaction. - */ - int GetMappedAction(vtkCommand::EventIds eid); - //@} - vtkSetClampMacro(GrabEnabled, int, 0, 1); - vtkGetMacro(GrabEnabled, int); - vtkBooleanMacro(GrabEnabled, int); - - vtkCellPicker* GetAccuratePicker(); - - /// Set physical to world magnification. Valid value range is [0.01, 100]. - /// Note: Conversion is physicalScale = 1000 / magnification - void SetMagnification(double magnification); - double GetMagnification(); - -protected: - - bool QuickPick(int x, int y, double pickPoint[3]); - -protected: - - /// For jump to slice feature (when mouse is moved while shift key is pressed) - vtkSmartPointer AccuratePicker; - vtkSmartPointer QuickPicker; - - int GrabEnabled; - -protected: - vtkVirtualRealityViewInteractorStyle(); - ~vtkVirtualRealityViewInteractorStyle() override; - - vtkWeakPointer DisplayableManagers; - -private: - vtkVirtualRealityViewInteractorStyle(const vtkVirtualRealityViewInteractorStyle&); /// Not implemented. - void operator=(const vtkVirtualRealityViewInteractorStyle&); /// Not implemented. - - class vtkInternal; - vtkInternal* Internal; -}; - -#endif diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyleDelegate.cxx b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyleDelegate.cxx new file mode 100644 index 0000000..f52be8f --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyleDelegate.cxx @@ -0,0 +1,541 @@ +/*============================================================================== + + Copyright (c) Kitware Inc. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc. + and was supported through the University of Western Ontario. + +==============================================================================*/ + +// VR Logic includes +#include "vtkSlicerVirtualRealityLogic.h" + +// VR MRML includes +#include "vtkMRMLVirtualRealityViewNode.h" + +// VR MRMLDM includes +#include "vtkVirtualRealityViewInteractorStyleDelegate.h" + +// MRML includes +#include +#include +#include +#include + +// MRMLDM includes +#include + +// VTK/Rendering/VR includes +#include +#include + +// VTK includes +#include +#include +#include +#include +#include +#include +#include + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(vtkVirtualRealityViewInteractorStyleDelegate); + +//---------------------------------------------------------------------------- +void vtkVirtualRealityViewInteractorStyleDelegate::SetInteractorStyle(vtkVRInteractorStyle* interactorStyle) +{ + vtkSetSmartPointerBodyMacro(InteractorStyle, vtkVRInteractorStyle, interactorStyle); + + if (interactorStyle != nullptr) + { + // Create default inputs mapping + + // The "eventId to state" mapping (see call to `MapInputToAction` below) applies to right and left + // controller buttons because they are bound to the same eventId: + // - `vtk_openvr_binding_*.json` files define the "button to action" mapping + // - `vtkOpenVRInteractorStyle()` contructor defines the "action to eventId" mapping + + interactorStyle->MapInputToAction(vtkCommand::ViewerMovement3DEvent, VTKIS_DOLLY); + interactorStyle->MapInputToAction(vtkCommand::Select3DEvent, VTKIS_POSITION_PROP); + } +} + +//--------------------------------------------------------------------------- +vtkMRMLScene* vtkVirtualRealityViewInteractorStyleDelegate::GetMRMLScene() const +{ + if (!this->DisplayableManagers + || this->DisplayableManagers->GetDisplayableManagerCount() == 0) + { + vtkErrorMacro("GetMRMLScene failed: DisplayableManagers is not set"); + return nullptr; + } + + return this->DisplayableManagers->GetNthDisplayableManager(0)->GetMRMLScene(); +} + +//---------------------------------------------------------------------------- +void vtkVirtualRealityViewInteractorStyleDelegate::OnPan() +{ + this->OnPinch3D(); +} + +//---------------------------------------------------------------------------- +void vtkVirtualRealityViewInteractorStyleDelegate::OnPinch() +{ + this->OnPinch3D(); +} + +//---------------------------------------------------------------------------- +void vtkVirtualRealityViewInteractorStyleDelegate::OnRotate() +{ + this->OnPinch3D(); +} + +//---------------------------------------------------------------------------- +void vtkVirtualRealityViewInteractorStyleDelegate::OnPinch3D() +{ + vtkVRInteractorStyle* istyle = this->InteractorStyle; + if (istyle == nullptr) + { + vtkErrorMacro("OnPinch3D failed: InteractorStyle is not set"); + return; + } + if (istyle->GetCurrentRenderer() == nullptr) + { + vtkErrorMacro("OnPinch3D failed: CurrentRenderer is not set"); + return; + } + + int rc = static_cast(vtkEventDataDevice::RightController); + int lc = static_cast(vtkEventDataDevice::LeftController); + if ( (this->PickedNode[rc] && this->PickedNode[rc]->GetSelectable()) + || (this->PickedNode[lc] && this->PickedNode[lc]->GetSelectable()) ) + { + // If a node is being picked then don't do the pinch 3D operation + return; + } + + istyle->SetInteractionState(vtkEventDataDevice::RightController, VTKIS_ZOOM); + istyle->SetInteractionState(vtkEventDataDevice::LeftController, VTKIS_ZOOM); + + if (!this->StartingControllerPoseValid) + { + // If starting pose is not valid then cannot do the pinch 3D operation + return; + } + + vtkVRRenderWindowInteractor* rwi = vtkVRRenderWindowInteractor::SafeDownCast(istyle->GetInteractor()); + vtkVRRenderWindow* rw = vtkVRRenderWindow::SafeDownCast(rwi->GetRenderWindow()); + + int pointer = rwi->GetPointerIndex(); + + istyle->FindPokedRenderer(rwi->GetEventPositions(pointer)[0], rwi->GetEventPositions(pointer)[1]); + + // Get current controller poses + vtkMatrix4x4* currentController0Pose_Physical = + rw->GetDeviceToPhysicalMatrixForDevice(vtkEventDataDevice::LeftController); + vtkMatrix4x4* currentController1Pose_Physical = + rw->GetDeviceToPhysicalMatrixForDevice(vtkEventDataDevice::RightController); + + // Get combined current controller pose + vtkNew combinedCurrentControllerPose; + if ( !vtkSlicerVirtualRealityLogic::CalculateCombinedControllerPose( + currentController0Pose_Physical, currentController1Pose_Physical, combinedCurrentControllerPose ) ) + { + // If combined pose is invalid, then use the last valid pose if it exists + if (this->LastValidCombinedControllerPoseExists) + { + combinedCurrentControllerPose->DeepCopy(this->LastValidCombinedControllerPose); + } + else + { + // There is no valid last position so cannot do the pinch 3D operation + return; + } + } + else + { + // Save current as last valid pose + this->LastValidCombinedControllerPose->DeepCopy(combinedCurrentControllerPose); + } + + // Calculate modifier matrix + vtkNew inverseCombinedCurrentControllerPose; + vtkMatrix4x4::Invert(combinedCurrentControllerPose, inverseCombinedCurrentControllerPose); + + vtkNew modifyPhysicalToWorldMatrix; + vtkMatrix4x4::Multiply4x4( + this->CombinedStartingControllerPose, inverseCombinedCurrentControllerPose, modifyPhysicalToWorldMatrix); + + // Calculate new physical to world matrix + vtkNew startingPhysicalToWorldMatrix; + rwi->GetStartingPhysicalToWorldMatrix(startingPhysicalToWorldMatrix); + vtkNew newPhysicalToWorldMatrix; + vtkMatrix4x4::Multiply4x4( + startingPhysicalToWorldMatrix, modifyPhysicalToWorldMatrix, newPhysicalToWorldMatrix); + + // Set new physical to world matrix + rw->SetPhysicalToWorldMatrix(newPhysicalToWorldMatrix); + if (istyle->GetAutoAdjustCameraClippingRange()) + { + istyle->GetCurrentRenderer()->ResetCameraClippingRange(); + } + if (rwi->GetLightFollowCamera()) + { + istyle->GetCurrentRenderer()->UpdateLightsGeometryToFollowCamera(); + } +} + +//---------------------------------------------------------------------------- +void vtkVirtualRealityViewInteractorStyleDelegate::StartGesture() +{ + vtkVRInteractorStyle* istyle = this->InteractorStyle; + if (istyle == nullptr) + { + vtkErrorMacro("StartGesture failed: InteractorStyle is not set"); + return; + } + + // Store combined starting controller pose + vtkVRRenderWindow* rw = vtkVRRenderWindow::SafeDownCast(istyle->GetInteractor()->GetRenderWindow()); + + vtkMatrix4x4* startingController0Pose_Physical = + rw->GetDeviceToPhysicalMatrixForDevice(vtkEventDataDevice::LeftController); + vtkMatrix4x4* startingController1Pose_Physical = + rw->GetDeviceToPhysicalMatrixForDevice(vtkEventDataDevice::RightController); + + if ( vtkSlicerVirtualRealityLogic::CalculateCombinedControllerPose( + startingController0Pose_Physical, startingController1Pose_Physical, this->CombinedStartingControllerPose) ) + { + this->StartingControllerPoseValid = true; + } +} + +//---------------------------------------------------------------------------- +void vtkVirtualRealityViewInteractorStyleDelegate::EndGesture() +{ + this->StartingControllerPoseValid = false; + this->LastValidCombinedControllerPoseExists = false; +} + +//---------------------------------------------------------------------------- +// Interaction methods +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +void vtkVirtualRealityViewInteractorStyleDelegate::PositionProp( + vtkEventData* ed, double* vtkNotUsed(lwpos), double* vtkNotUsed(lwori)) +{ + vtkVRInteractorStyle* istyle = this->InteractorStyle; + if (istyle == nullptr) + { + vtkErrorMacro("PositionProp failed: InteractorStyle is not set"); + return; + } + if (istyle->GetCurrentRenderer() == nullptr) + { + vtkErrorMacro("PositionProp failed: CurrentRenderer is not set"); + return; + } + if (this->DisplayableManagers == nullptr) + { + vtkErrorMacro("PositionProp failed: DisplayableManagers is not set"); + return; + } + + vtkEventDataDevice3D *edd = ed->GetAsEventDataDevice3D(); + if (edd == nullptr) + { + vtkErrorMacro("PositionProp failed: 3D event is expected"); + return; + } + int deviceIndex = static_cast(edd->GetDevice()); + vtkMRMLDisplayableNode* pickedNode = this->PickedNode[deviceIndex]; + if (pickedNode == nullptr || !pickedNode->GetSelectable()) + { + return; + } + if (ed->GetType() != vtkCommand::Move3DEvent) + { + return; + } + + // Get positions and orientations + vtkRenderWindowInteractor3D* rwi = vtkRenderWindowInteractor3D::SafeDownCast(istyle->GetInteractor()); + double worldPos[3] = {0.0}; + edd->GetWorldPosition(worldPos); + double* lastWorldPos = rwi->GetLastWorldEventPosition(rwi->GetPointerIndex()); + double* worldOrientation = rwi->GetWorldEventOrientation(rwi->GetPointerIndex()); + double* lastWorldOrientation = rwi->GetLastWorldEventOrientation(rwi->GetPointerIndex()); + + // Calculate transform + vtkNew interactionTransform; + interactionTransform->PreMultiply(); + + double translation[3] = {0.0}; + for (int i = 0; i < 3; i++) + { + translation[i] = worldPos[i] - lastWorldPos[i]; + } + vtkQuaternion q1; + q1.SetRotationAngleAndAxis(vtkMath::RadiansFromDegrees( + lastWorldOrientation[0]), lastWorldOrientation[1], lastWorldOrientation[2], lastWorldOrientation[3]); + vtkQuaternion q2; + q2.SetRotationAngleAndAxis(vtkMath::RadiansFromDegrees( + worldOrientation[0]), worldOrientation[1], worldOrientation[2], worldOrientation[3]); + q1.Conjugate(); + q2 = q2*q1; + double axis[4] = {0.0}; + axis[0] = vtkMath::DegreesFromRadians(q2.GetRotationAngleAndAxis(axis+1)); + interactionTransform->Translate(worldPos[0], worldPos[1], worldPos[2]); + interactionTransform->RotateWXYZ(axis[0], axis[1], axis[2], axis[3]); + interactionTransform->Translate(-worldPos[0], -worldPos[1], -worldPos[2]); + interactionTransform->Translate(translation[0], translation[1], translation[2]); + + // Make sure that the topmost parent transform is the VR interaction transform + vtkMRMLTransformNode* topTransformNode = pickedNode->GetParentTransformNode(); + while (topTransformNode && topTransformNode->GetParentTransformNode()) + { + topTransformNode = topTransformNode->GetParentTransformNode(); + } + vtkMRMLTransformNode* vrTransformNode = nullptr; + if (!topTransformNode) + { + // Create interaction transform if no transform found + vtkNew newTransformNode; + std::string vrTransformNodeName(pickedNode->GetName()); + vrTransformNodeName.append("_VR_Interaction_Transform"); + newTransformNode->SetName(vrTransformNodeName.c_str()); + newTransformNode->SetAttribute( + vtkMRMLVirtualRealityViewNode::GetVirtualRealityInteractionTransformAttributeName(), "1"); + pickedNode->GetScene()->AddNode(newTransformNode); + pickedNode->SetAndObserveTransformNodeID(newTransformNode->GetID()); + vrTransformNode = newTransformNode.GetPointer(); + } + else + { + // Make top transform the VR interaction transform + if (!topTransformNode->GetAttribute( + vtkMRMLVirtualRealityViewNode::GetVirtualRealityInteractionTransformAttributeName()) ) + { + vtkInfoMacro("PositionProp: Transform " << topTransformNode->GetName() << + " is now used as VR interaction transform for node " << pickedNode->GetName()); + topTransformNode->SetAttribute( + vtkMRMLVirtualRealityViewNode::GetVirtualRealityInteractionTransformAttributeName(), "1"); + } + // VR interaction transform found (i.e. the top transform has the VR interaction transform attribute) + vrTransformNode = topTransformNode; + } + + // Apply interaction transform + vtkTransform* lastVrInteractionTransform = vtkTransform::SafeDownCast(vrTransformNode->GetTransformToParent()); + if (vrTransformNode->GetTransformToParent() && !lastVrInteractionTransform) + { + //TODO: Remove constraint + vtkErrorMacro("PositionProp failed: Node " << pickedNode->GetName() << " contains non-linear transform"); + return; + } + // Use the interaction transform with the flattened matrix to prevent concatenation + // of potentially thousands of transforms + if (lastVrInteractionTransform) + { + interactionTransform->Concatenate(lastVrInteractionTransform->GetMatrix()); + lastVrInteractionTransform->DeepCopy(interactionTransform); + } + else + { + vrTransformNode->SetAndObserveTransformToParent(interactionTransform); + } + + if (istyle->GetAutoAdjustCameraClippingRange()) + { + istyle->GetCurrentRenderer()->ResetCameraClippingRange(); + } +} + +//---------------------------------------------------------------------------- +// Interaction entry points +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +void vtkVirtualRealityViewInteractorStyleDelegate::StartPositionProp(vtkEventDataDevice3D* edata) +{ + vtkVRInteractorStyle* istyle = this->InteractorStyle; + if (istyle == nullptr) + { + vtkErrorMacro("StartPositionProp failed: InteractorStyle is not set"); + return; + } + + if (this->DisplayableManagers == nullptr) + { + vtkErrorMacro("StartPositionProp failed: DisplayableManagers is not set"); + return; + } + + vtkMRMLScene* scene = this->GetMRMLScene(); + if (scene == nullptr) + { + vtkErrorMacro("StartPositionProp failed: scenee is not set"); + return; + } + + vtkEventDataDevice device = edata->GetDevice(); + + double pos[3] = {0.0}; + edata->GetWorldPosition(pos); + + // Get MRML node to move + for (int i=0; iDisplayableManagers->GetDisplayableManagerCount(); ++i) + { + vtkMRMLAbstractThreeDViewDisplayableManager* currentDisplayableManager = + vtkMRMLAbstractThreeDViewDisplayableManager::SafeDownCast(this->DisplayableManagers->GetNthDisplayableManager(i)); + if (currentDisplayableManager == nullptr) + { + continue; + } + currentDisplayableManager->Pick3D(pos); + vtkMRMLDisplayNode* displayNode = vtkMRMLDisplayNode::SafeDownCast( + scene->GetNodeByID(currentDisplayableManager->GetPickedNodeID()) ); + if (displayNode == nullptr) + { + continue; + } + //TODO: Only the first selectable picked node in the last displayable manager will be picked + vtkMRMLDisplayableNode* pickedNode = displayNode->GetDisplayableNode(); + if (pickedNode != nullptr && pickedNode->GetSelectable() && this->GrabEnabled) + { + this->PickedNode[static_cast(device)] = pickedNode; + } + } + + istyle->SetInteractionState(device, VTKIS_POSITION_PROP); + + // Don't start action if a controller is already positioning the prop + int rc = static_cast(vtkEventDataDevice::RightController); + int lc = static_cast(vtkEventDataDevice::LeftController); + if (this->PickedNode[rc] == this->PickedNode[lc]) + { + this->EndPositionProp(edata); + return; + } +} + +//---------------------------------------------------------------------------- +void vtkVirtualRealityViewInteractorStyleDelegate::EndPositionProp(vtkEventDataDevice3D* edata) +{ + vtkVRInteractorStyle* istyle = this->InteractorStyle; + if (istyle == nullptr) + { + vtkErrorMacro("EndPositionProp failed: InteractorStyle is not set"); + return; + } + + vtkEventDataDevice device = edata->GetDevice(); + istyle->SetInteractionState(device, VTKIS_NONE); + this->PickedNode[static_cast(device)] = nullptr; +} + +//---------------------------------------------------------------------------- +void vtkVirtualRealityViewInteractorStyleDelegate::SetMagnification(double magnification) +{ + vtkVRInteractorStyle* istyle = this->InteractorStyle; + if (istyle == nullptr) + { + vtkErrorMacro("SetMagnification failed: InteractorStyle is not set"); + return; + } + + if (!istyle->GetCurrentRenderer()) + { + return; + } + + vtkVRRenderWindow* rw = vtkVRRenderWindow::SafeDownCast(istyle->GetInteractor()->GetRenderWindow()); + + double currentPhysicalScale = rw->GetPhysicalScale(); + double newPhysicalScale = 1000.0 / magnification; + double scaleFactor = newPhysicalScale / currentPhysicalScale; + if (fabs(scaleFactor - 1.0) < 0.001) + { + return; + } + + // Get Physical to World_Origin matrix + vtkNew physicalToWorldOriginMatrix; + rw->GetPhysicalToWorldMatrix(physicalToWorldOriginMatrix); + + // Calculate World_Origin to World_FocusPoint matrix + double bounds[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; + istyle->GetCurrentRenderer()->ComputeVisiblePropBounds(bounds); + double focusPoint_World[3] = { (bounds[1] + bounds[0]) / 2.0, + (bounds[3] + bounds[2]) / 2.0, + (bounds[5] + bounds[4]) / 2.0 }; + vtkNew worldOriginToWorldFocusPointMatrix; + for (int i=0; i<3; ++i) + { + worldOriginToWorldFocusPointMatrix->SetElement(i,3, -focusPoint_World[i]); + } + + // Calculate World_FocusPoint to World_ScaledFocusPoint matrix + vtkNew worldFocusPointToWorldScaledFocusPointMatrix; + for (int i=0; i<3; ++i) + { + worldFocusPointToWorldScaledFocusPointMatrix->SetElement(i,i, scaleFactor); + } + + // Calculate World_ScaledFocusPoint to World_ScaledOrigin matrix + vtkNew worldScaledFocusPointToWorldScaledOriginMatrix; + for (int i=0; i<3; ++i) + { + worldScaledFocusPointToWorldScaledOriginMatrix->SetElement(i,3, focusPoint_World[i]); + } + + // Calculate Physical to World_ScaledOrigin matrix + vtkNew worldFocusPointToWorldScaledOriginMatrix; + vtkMatrix4x4::Multiply4x4( worldScaledFocusPointToWorldScaledOriginMatrix, worldFocusPointToWorldScaledFocusPointMatrix, + worldFocusPointToWorldScaledOriginMatrix ); + + vtkNew worldOriginToWorldScaledOriginMatrix; + vtkMatrix4x4::Multiply4x4( worldFocusPointToWorldScaledOriginMatrix, worldOriginToWorldFocusPointMatrix, + worldOriginToWorldScaledOriginMatrix ); + + // Physical to World_ScaledOrigin + vtkNew physicalToWorldScaledOriginMatrix; + vtkMatrix4x4::Multiply4x4( worldOriginToWorldScaledOriginMatrix, physicalToWorldOriginMatrix, + physicalToWorldScaledOriginMatrix ); + + rw->SetPhysicalToWorldMatrix(physicalToWorldScaledOriginMatrix); + + if (istyle->GetAutoAdjustCameraClippingRange()) + { + istyle->GetCurrentRenderer()->ResetCameraClippingRange(); + } + if (istyle->GetInteractor()->GetLightFollowCamera()) + { + istyle->GetCurrentRenderer()->UpdateLightsGeometryToFollowCamera(); + } +} + +//--------------------------------------------------------------------------- +double vtkVirtualRealityViewInteractorStyleDelegate::GetMagnification() +{ + vtkVRInteractorStyle* istyle = this->InteractorStyle; + if (istyle == nullptr) + { + vtkErrorMacro("GetMagnification failed: InteractorStyle is not set"); + return 1.0; + } + + vtkVRRenderWindow* rw = vtkVRRenderWindow::SafeDownCast(istyle->GetInteractor()->GetRenderWindow()); + return 1000.0 / rw->GetPhysicalScale(); +} diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyleDelegate.h b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyleDelegate.h new file mode 100644 index 0000000..5fe7465 --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewInteractorStyleDelegate.h @@ -0,0 +1,125 @@ +/*============================================================================== + + Copyright (c) Kitware Inc. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Jean-Christophe Fillion-Robin, Kitware Inc. + and was supported through the University of Western Ontario. + +==============================================================================*/ + +#ifndef vtkVirtualRealityViewInteractorStyleDelegate_h +#define vtkVirtualRealityViewInteractorStyleDelegate_h + +// VR MRMLDM includes +#include "vtkSlicerVirtualRealityModuleMRMLDisplayableManagerExport.h" + +// MRML includes +class vtkMRMLDisplayableNode; +class vtkMRMLScene; + +// MRMLDM includes +#include + +// VTK/Rendering/VR includes +#include + +// VTK includes +#include +#include +#include +#include +#include +#include + + +class VTK_SLICER_VIRTUALREALITY_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkVirtualRealityViewInteractorStyleDelegate + : public vtkObject +{ +public: + static vtkVirtualRealityViewInteractorStyleDelegate *New(); + typedef vtkVirtualRealityViewInteractorStyleDelegate Self; + vtkTypeMacro(vtkVirtualRealityViewInteractorStyleDelegate,vtkObject); + + ///@{ + /// Interactor style to perform the complex gesture from. + void SetInteractorStyle(vtkVRInteractorStyle* interactor); + vtkGetSmartPointerMacro(InteractorStyle, vtkVRInteractorStyle); + ///}@ + + ///@{ + /// Set/get displayable managers + vtkSetSmartPointerMacro(DisplayableManagers, vtkMRMLDisplayableManagerGroup); + vtkGetSmartPointerMacro(DisplayableManagers, vtkMRMLDisplayableManagerGroup); + ///}@ + + /// Get MRML scene from the displayable manager group (the first displayable manager's if any) + /// \sa GetDisplayableManagers() + vtkMRMLScene* GetMRMLScene() const; + + ///@{ + /// Multitouch events binding delegates. + void StartGesture(); + void EndGesture(); + void OnPan(); + void OnPinch(); + void OnRotate(); + ///@} + + ///@{ + /// Interaction mode entry points delegates. + void StartPositionProp(vtkEventDataDevice3D *); + void EndPositionProp(vtkEventDataDevice3D *); + ///}@ + + ///@{ + /// Interaction method delegates. + void PositionProp(vtkEventData*, double* lwpos = nullptr, double* lwori = nullptr); + ///@} + + ///@{ + /// Enable/disable picking. + vtkSetMacro(GrabEnabled, bool) + vtkGetMacro(GrabEnabled, bool); + vtkBooleanMacro(GrabEnabled, bool); + ///@} + + ///@{ + /// Set physical to world magnification. Valid value range is [0.01, 100]. + /// Note: Conversion is physicalScale = 1000 / magnification + void SetMagnification(double magnification); + double GetMagnification(); + ///}@ + +protected: + vtkWeakPointer InteractorStyle; + + void OnPinch3D(); + + vtkNew CombinedStartingControllerPose; + bool StartingControllerPoseValid{false}; + + vtkNew LastValidCombinedControllerPose; + bool LastValidCombinedControllerPoseExists{false}; + + bool GrabEnabled{true}; + vtkWeakPointer PickedNode[vtkEventDataNumberOfDevices]; + vtkWeakPointer DisplayableManagers; + +private: + vtkVirtualRealityViewInteractorStyleDelegate() = default; + ~vtkVirtualRealityViewInteractorStyleDelegate() override = default; + + vtkVirtualRealityViewInteractorStyleDelegate(const vtkVirtualRealityViewInteractorStyleDelegate&) = delete; + void operator=(const vtkVirtualRealityViewInteractorStyleDelegate&) = delete; +}; + +#endif diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractor.cxx b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractor.cxx new file mode 100644 index 0000000..bc41300 --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractor.cxx @@ -0,0 +1,28 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through CANARIE's Research Software Program, and Cancer + Care Ontario. + +==============================================================================*/ + +// VR MRMLDM includes +#include "vtkVirtualRealityViewOpenVRInteractor.h" + +// VTK includes +#include + +//------------------------------------------------------------------------------ +vtkStandardNewMacro(vtkVirtualRealityViewOpenVRInteractor); diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractor.h b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractor.h new file mode 100644 index 0000000..39e7c46 --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractor.h @@ -0,0 +1,68 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through CANARIE's Research Software Program, and Cancer + Care Ontario. + +==============================================================================*/ + +#ifndef vtkVirtualRealityViewOpenVRInteractor_h +#define vtkVirtualRealityViewOpenVRInteractor_h + +// VR MRMLDM includes +#include "vtkSlicerVirtualRealityModuleMRMLDisplayableManagerExport.h" +#include "vtkVirtualRealityComplexGestureRecognizer.h" + +// VTK Rendering/OpenVR includes +#include + +// VTK includes +#include + + +class VTK_SLICER_VIRTUALREALITY_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkVirtualRealityViewOpenVRInteractor + : public vtkOpenVRRenderWindowInteractor +{ +public: + static vtkVirtualRealityViewOpenVRInteractor *New(); + vtkTypeMacro(vtkVirtualRealityViewOpenVRInteractor,vtkOpenVRRenderWindowInteractor); + + ///@{ + /// Define Slicer specific heuristic for handling complex gestures. + virtual void HandleComplexGestureEvents(vtkEventData* ed) override + { + this->ComplexGestureRecognizer->HandleComplexGestureEvents(ed); + } + virtual void RecognizeComplexGesture(vtkEventDataDevice3D* edata) override + { + this->ComplexGestureRecognizer->RecognizeComplexGesture(edata); + } + ///@} + +protected: + vtkNew ComplexGestureRecognizer; + +private: + vtkVirtualRealityViewOpenVRInteractor() + { + this->ComplexGestureRecognizer->SetInteractor(this); + } + ~vtkVirtualRealityViewOpenVRInteractor() override = default; + + vtkVirtualRealityViewOpenVRInteractor(const vtkVirtualRealityViewOpenVRInteractor&) = delete; + void operator=(const vtkVirtualRealityViewOpenVRInteractor&) = delete; +}; + +#endif diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractorStyle.cxx b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractorStyle.cxx new file mode 100644 index 0000000..fd2271d --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractorStyle.cxx @@ -0,0 +1,28 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through CANARIE's Research Software Program, and Cancer + Care Ontario. + +==============================================================================*/ + +// VR MRMLDM includes +#include "vtkVirtualRealityViewOpenVRInteractorStyle.h" + +// VTK includes +#include + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(vtkVirtualRealityViewOpenVRInteractorStyle); diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractorStyle.h b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractorStyle.h new file mode 100644 index 0000000..1bfb455 --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenVRInteractorStyle.h @@ -0,0 +1,102 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through CANARIE's Research Software Program, and Cancer + Care Ontario. + +==============================================================================*/ + +#ifndef vtkVirtualRealityViewOpenVRInteractorStyle_h +#define vtkVirtualRealityViewOpenVRInteractorStyle_h + +// VR MRMLDM includes +#include "vtkSlicerVirtualRealityModuleMRMLDisplayableManagerExport.h" +#include "vtkVirtualRealityViewInteractorStyleDelegate.h" + +// VTK Rendering/OpenVR +#include + +// VTK includes +#include +#include +#include +#include + +class vtkMRMLScene; +class vtkMRMLDisplayableManagerGroup; +class vtkWorldPointPicker; + + +class VTK_SLICER_VIRTUALREALITY_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkVirtualRealityViewOpenVRInteractorStyle + : public vtkOpenVRInteractorStyle +{ +public: + static vtkVirtualRealityViewOpenVRInteractorStyle *New(); + vtkTypeMacro(vtkVirtualRealityViewOpenVRInteractorStyle,vtkOpenVRInteractorStyle); + + ///@{ + /// Set/get delegate + void SetInteractorStyleDelegate(vtkVirtualRealityViewInteractorStyleDelegate* delegate) + { + vtkSetSmartPointerBodyMacro(InteractorStyleDelegate, vtkVirtualRealityViewInteractorStyleDelegate, delegate); + if (delegate != nullptr) + { + delegate->SetInteractorStyle(this); + } + } + vtkGetSmartPointerMacro(InteractorStyleDelegate, vtkVirtualRealityViewInteractorStyleDelegate); + ///}@ + + //@{ + /** + * Interaction mode entry points. + */ + void StartPositionProp(vtkEventDataDevice3D * edata) override { this->InteractorStyleDelegate->StartPositionProp(edata); } + void EndPositionProp(vtkEventDataDevice3D * edata) override { this->InteractorStyleDelegate->EndPositionProp(edata); } + //@} + + //@{ + /** + * Multitouch events binding. + */ + void StartGesture() override { this->InteractorStyleDelegate->StartGesture(); } + void EndGesture() override { this->InteractorStyleDelegate->EndGesture(); } + void OnPan() override { this->InteractorStyleDelegate->OnPan(); } + void OnPinch() override { this->InteractorStyleDelegate->OnPinch(); } + void OnRotate() override { this->InteractorStyleDelegate->OnRotate(); } + //@} + + //@{ + /** + * Methods for interaction. + */ + void PositionProp(vtkEventData* ed, double* lwpos = nullptr, double* lwori = nullptr) override + { + this->InteractorStyleDelegate->PositionProp(ed, lwpos, lwori); + } + //@} + +protected: + vtkVirtualRealityViewOpenVRInteractorStyle() = default; + ~vtkVirtualRealityViewOpenVRInteractorStyle() override = default; + + vtkSmartPointer InteractorStyleDelegate; + +private: + vtkVirtualRealityViewOpenVRInteractorStyle(const vtkVirtualRealityViewOpenVRInteractorStyle&) = delete; + void operator=(const vtkVirtualRealityViewOpenVRInteractorStyle&) = delete; +}; + +#endif diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractor.cxx b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractor.cxx new file mode 100644 index 0000000..7f26141 --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractor.cxx @@ -0,0 +1,28 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through CANARIE's Research Software Program, and Cancer + Care Ontario. + +==============================================================================*/ + +// VR MRMLDM includes +#include "vtkVirtualRealityViewOpenXRInteractor.h" + +// VTK includes +#include + +//------------------------------------------------------------------------------ +vtkStandardNewMacro(vtkVirtualRealityViewOpenXRInteractor); diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractor.h b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractor.h new file mode 100644 index 0000000..ff104d9 --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractor.h @@ -0,0 +1,68 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through CANARIE's Research Software Program, and Cancer + Care Ontario. + +==============================================================================*/ + +#ifndef vtkVirtualRealityViewOpenXRInteractor_h +#define vtkVirtualRealityViewOpenXRInteractor_h + +// VR MRMLDM includes +#include "vtkSlicerVirtualRealityModuleMRMLDisplayableManagerExport.h" +#include "vtkVirtualRealityComplexGestureRecognizer.h" + +// VTK Rendering/OpenXR includes +#include + +// VTK includes +#include + + +class VTK_SLICER_VIRTUALREALITY_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkVirtualRealityViewOpenXRInteractor + : public vtkOpenXRRenderWindowInteractor +{ +public: + static vtkVirtualRealityViewOpenXRInteractor *New(); + vtkTypeMacro(vtkVirtualRealityViewOpenXRInteractor,vtkOpenXRRenderWindowInteractor); + + ///@{ + /// Define Slicer specific heuristic for handling complex gestures. + virtual void HandleComplexGestureEvents(vtkEventData* ed) override + { + this->ComplexGestureRecognizer->HandleComplexGestureEvents(ed); + } + virtual void RecognizeComplexGesture(vtkEventDataDevice3D* edata) override + { + this->ComplexGestureRecognizer->RecognizeComplexGesture(edata); + } + ///@} + +protected: + vtkNew ComplexGestureRecognizer; + +private: + vtkVirtualRealityViewOpenXRInteractor() + { + this->ComplexGestureRecognizer->SetInteractor(this); + } + ~vtkVirtualRealityViewOpenXRInteractor() override = default; + + vtkVirtualRealityViewOpenXRInteractor(const vtkVirtualRealityViewOpenXRInteractor&) = delete; + void operator=(const vtkVirtualRealityViewOpenXRInteractor&) = delete; +}; + +#endif diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractorStyle.cxx b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractorStyle.cxx new file mode 100644 index 0000000..b0157a0 --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractorStyle.cxx @@ -0,0 +1,28 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through CANARIE's Research Software Program, and Cancer + Care Ontario. + +==============================================================================*/ + +// VR MRMLDM includes +#include "vtkVirtualRealityViewOpenXRInteractorStyle.h" + +// VTK includes +#include + +//---------------------------------------------------------------------------- +vtkStandardNewMacro(vtkVirtualRealityViewOpenXRInteractorStyle); diff --git a/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractorStyle.h b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractorStyle.h new file mode 100644 index 0000000..af6b854 --- /dev/null +++ b/VirtualReality/MRMLDM/vtkVirtualRealityViewOpenXRInteractorStyle.h @@ -0,0 +1,102 @@ +/*============================================================================== + + Copyright (c) Laboratory for Percutaneous Surgery (PerkLab) + Queen's University, Kingston, ON, Canada. All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + 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. + + This file was originally developed by Csaba Pinter, PerkLab, Queen's University + and was supported through CANARIE's Research Software Program, and Cancer + Care Ontario. + +==============================================================================*/ + +#ifndef vtkVirtualRealityViewOpenXRInteractorStyle_h +#define vtkVirtualRealityViewOpenXRInteractorStyle_h + +// VR MRMLDM includes +#include "vtkSlicerVirtualRealityModuleMRMLDisplayableManagerExport.h" +#include "vtkVirtualRealityViewInteractorStyleDelegate.h" + +// VTK Rendering/OpenXR includes +#include + +// VTK includes +#include +#include +#include +#include + +class vtkMRMLScene; +class vtkMRMLDisplayableManagerGroup; +class vtkWorldPointPicker; + + +class VTK_SLICER_VIRTUALREALITY_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkVirtualRealityViewOpenXRInteractorStyle + : public vtkOpenXRInteractorStyle +{ +public: + static vtkVirtualRealityViewOpenXRInteractorStyle *New(); + vtkTypeMacro(vtkVirtualRealityViewOpenXRInteractorStyle,vtkOpenXRInteractorStyle); + + ///@{ + /// Set/get delegate + void SetInteractorStyleDelegate(vtkVirtualRealityViewInteractorStyleDelegate* delegate) + { + vtkSetSmartPointerBodyMacro(InteractorStyleDelegate, vtkVirtualRealityViewInteractorStyleDelegate, delegate); + if (delegate != nullptr) + { + delegate->SetInteractorStyle(this); + } + } + vtkGetSmartPointerMacro(InteractorStyleDelegate, vtkVirtualRealityViewInteractorStyleDelegate); + ///}@ + + //@{ + /** + * Interaction mode entry points. + */ + void StartPositionProp(vtkEventDataDevice3D * edata) override { this->InteractorStyleDelegate->StartPositionProp(edata); } + void EndPositionProp(vtkEventDataDevice3D * edata) override { this->InteractorStyleDelegate->EndPositionProp(edata); } + //@} + + //@{ + /** + * Multitouch events binding. + */ + void StartGesture() override { this->InteractorStyleDelegate->StartGesture(); } + void EndGesture() override { this->InteractorStyleDelegate->EndGesture(); } + void OnPan() override { this->InteractorStyleDelegate->OnPan(); } + void OnPinch() override { this->InteractorStyleDelegate->OnPinch(); } + void OnRotate() override { this->InteractorStyleDelegate->OnRotate(); } + //@} + + //@{ + /** + * Methods for interaction. + */ + void PositionProp(vtkEventData* ed, double* lwpos = nullptr, double* lwori = nullptr) override + { + this->InteractorStyleDelegate->PositionProp(ed, lwpos, lwori); + } + //@} + +protected: + vtkVirtualRealityViewOpenXRInteractorStyle() = default; + ~vtkVirtualRealityViewOpenXRInteractorStyle() override = default; + + vtkSmartPointer InteractorStyleDelegate; + +private: + vtkVirtualRealityViewOpenXRInteractorStyle(const vtkVirtualRealityViewOpenXRInteractorStyle&) = delete; + void operator=(const vtkVirtualRealityViewOpenXRInteractorStyle&) = delete; +}; + +#endif diff --git a/VirtualReality/Resources/UI/qSlicerVirtualRealityModuleWidget.ui b/VirtualReality/Resources/UI/qSlicerVirtualRealityModuleWidget.ui index b2df421..e439275 100644 --- a/VirtualReality/Resources/UI/qSlicerVirtualRealityModuleWidget.ui +++ b/VirtualReality/Resources/UI/qSlicerVirtualRealityModuleWidget.ui @@ -6,8 +6,8 @@ 0 0 - 318 - 489 + 367 + 632 @@ -27,13 +27,23 @@ + + + Desired XR runtime: + + + + + + + Connect to hardware: - + @@ -90,14 +100,14 @@ - + Enable rendering: - + @@ -267,7 +277,7 @@ - + diff --git a/VirtualReality/SubjectHierarchyPlugins/qSlicerSubjectHierarchyVirtualRealityPlugin.cxx b/VirtualReality/SubjectHierarchyPlugins/qSlicerSubjectHierarchyVirtualRealityPlugin.cxx index 25f80e3..b71223e 100644 --- a/VirtualReality/SubjectHierarchyPlugins/qSlicerSubjectHierarchyVirtualRealityPlugin.cxx +++ b/VirtualReality/SubjectHierarchyPlugins/qSlicerSubjectHierarchyVirtualRealityPlugin.cxx @@ -36,8 +36,8 @@ #include // Qt includes -#include #include +#include //----------------------------------------------------------------------------- /// \ingroup Slicer_QtModules_SubjectHierarchy_Plugins diff --git a/VirtualReality/SubjectHierarchyPlugins/qSlicerSubjectHierarchyVirtualRealityPlugin.h b/VirtualReality/SubjectHierarchyPlugins/qSlicerSubjectHierarchyVirtualRealityPlugin.h index 4370427..1f707be 100644 --- a/VirtualReality/SubjectHierarchyPlugins/qSlicerSubjectHierarchyVirtualRealityPlugin.h +++ b/VirtualReality/SubjectHierarchyPlugins/qSlicerSubjectHierarchyVirtualRealityPlugin.h @@ -21,11 +21,12 @@ #ifndef __qSlicerSubjectHierarchyVirtualRealityPlugin_h #define __qSlicerSubjectHierarchyVirtualRealityPlugin_h +// VR SubjectHierarchy includes +#include "qSlicerVirtualRealitySubjectHierarchyPluginsExport.h" + // SubjectHierarchy includes #include "qSlicerSubjectHierarchyAbstractPlugin.h" -#include "qSlicerVirtualRealitySubjectHierarchyPluginsExport.h" - class qSlicerSubjectHierarchyVirtualRealityPluginPrivate; /// \ingroup Slicer_QtModules_SubjectHierarchy_Plugins diff --git a/VirtualReality/Testing/Cxx/CMakeLists.txt b/VirtualReality/Testing/Cxx/CMakeLists.txt index b7341cd..d5a000c 100644 --- a/VirtualReality/Testing/Cxx/CMakeLists.txt +++ b/VirtualReality/Testing/Cxx/CMakeLists.txt @@ -2,7 +2,8 @@ set(KIT qSlicer${MODULE_NAME}Module) #----------------------------------------------------------------------------- set(KIT_TEST_SRCS - #qSlicer${MODULE_NAME}ModuleTest.cxx + vtkMRMLVirtualRealityLayoutNodeTest1.cxx + vtkMRMLVirtualRealityViewNodeTest1.cxx ) #----------------------------------------------------------------------------- @@ -14,4 +15,5 @@ slicerMacroConfigureModuleCxxTestDriver( ) #----------------------------------------------------------------------------- -#simple_test(qSlicer${MODULE_NAME}ModuleTest) +simple_test(vtkMRMLVirtualRealityLayoutNodeTest1) +simple_test(vtkMRMLVirtualRealityViewNodeTest1) diff --git a/VirtualReality/Testing/Cxx/vtkMRMLVirtualRealityLayoutNodeTest1.cxx b/VirtualReality/Testing/Cxx/vtkMRMLVirtualRealityLayoutNodeTest1.cxx new file mode 100644 index 0000000..5e6ca41 --- /dev/null +++ b/VirtualReality/Testing/Cxx/vtkMRMLVirtualRealityLayoutNodeTest1.cxx @@ -0,0 +1,14 @@ + +// VirtualReality MRML includes +#include + +// MRML includes +#include + +int vtkMRMLVirtualRealityLayoutNodeTest1(int , char * []) +{ + vtkNew node1; + EXERCISE_ALL_BASIC_MRML_METHODS(node1.GetPointer()); + + return EXIT_SUCCESS; +} diff --git a/VirtualReality/Testing/Cxx/vtkMRMLVirtualRealityViewNodeTest1.cxx b/VirtualReality/Testing/Cxx/vtkMRMLVirtualRealityViewNodeTest1.cxx new file mode 100644 index 0000000..e276523 --- /dev/null +++ b/VirtualReality/Testing/Cxx/vtkMRMLVirtualRealityViewNodeTest1.cxx @@ -0,0 +1,21 @@ + +// VirtualReality MRML includes +#include + +// MRML includes +#include + +int vtkMRMLVirtualRealityViewNodeTest1(int , char * []) +{ + vtkNew node1; + EXERCISE_ALL_BASIC_MRML_METHODS(node1.GetPointer()); + + CHECK_INT(vtkMRMLVirtualRealityViewNode::GetXRRuntimeFromString(nullptr), -1); + CHECK_INT(vtkMRMLVirtualRealityViewNode::GetXRRuntimeFromString(""), -1); + CHECK_INT(vtkMRMLVirtualRealityViewNode::GetXRRuntimeFromString("any"), -1); + CHECK_INT(vtkMRMLVirtualRealityViewNode::GetXRRuntimeFromString("undefined"), vtkMRMLVirtualRealityViewNode::UndefinedXRRuntime); + CHECK_INT(vtkMRMLVirtualRealityViewNode::GetXRRuntimeFromString("OpenVR"), vtkMRMLVirtualRealityViewNode::OpenVR); + CHECK_INT(vtkMRMLVirtualRealityViewNode::GetXRRuntimeFromString("OpenXR"), vtkMRMLVirtualRealityViewNode::OpenXR); + + return EXIT_SUCCESS; +} diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityTransformWidget.cxx b/VirtualReality/Widgets/qMRMLVirtualRealityTransformWidget.cxx index e0a25d9..e252358 100644 --- a/VirtualReality/Widgets/qMRMLVirtualRealityTransformWidget.cxx +++ b/VirtualReality/Widgets/qMRMLVirtualRealityTransformWidget.cxx @@ -15,63 +15,72 @@ ==============================================================================*/ -// Slicer includes -#include -#include -#include +// VR MRML includes +#include "vtkMRMLVirtualRealityViewNode.h" -// qMRML includes +// VR Widgts includes #include "qMRMLVirtualRealityTransformWidget.h" #include "ui_qMRMLVirtualRealityTransformWidget.h" +// Qt includes +#include +#include +#include + // MRML includes #include -// OpenVR includes -#include +// VTK includes +#include -// SlicerVR includes -#include -#include - -// Qt includes -#include -#include namespace { - vr::ETrackingResult StringToPoseStatus(const char* cpose) + // Copied from https://github.com/ValveSoftware/openvr/blob/v2.0.10/headers/openvr.h + enum ETrackingResult + { + TrackingResult_Uninitialized = 1, + + TrackingResult_Calibrating_InProgress = 100, + TrackingResult_Calibrating_OutOfRange = 101, + + TrackingResult_Running_OK = 200, + TrackingResult_Running_OutOfRange = 201, + + TrackingResult_Fallback_RotationOnly = 300, + }; + + ::ETrackingResult StringToPoseStatus(const char* cpose) { if (cpose == nullptr) { - return vr::TrackingResult_Uninitialized; + return ::TrackingResult_Uninitialized; } std::string pose(cpose); if (pose.compare("CalibratingInProgress") == 0) { - return vr::TrackingResult_Calibrating_InProgress; + return ::TrackingResult_Calibrating_InProgress; } else if (pose.compare("CalibratingOutOfRange") == 0) { - return vr::TrackingResult_Calibrating_OutOfRange; + return ::TrackingResult_Calibrating_OutOfRange; } else if (pose.compare("RunningOk") == 0) { - return vr::TrackingResult_Running_OK; + return ::TrackingResult_Running_OK; } else if (pose.compare("RunningOutOfRange") == 0) { - return vr::TrackingResult_Running_OutOfRange; + return ::TrackingResult_Running_OutOfRange; } else { - return vr::TrackingResult_Uninitialized; + return ::TrackingResult_Uninitialized; } } } //----------------------------------------------------------------------------- -/// \ingroup Slicer_QtModules_Markups class qMRMLVirtualRealityTransformWidgetPrivate : public Ui_qMRMLVirtualRealityTransformWidget { @@ -84,8 +93,8 @@ class qMRMLVirtualRealityTransformWidgetPrivate vtkWeakPointer VRViewNode; vtkWeakPointer TransformNode; - vr::ETrackedDeviceClass TransformType; - vr::ETrackingResult PreviousStatus; + vtkEventDataDevice TransformType; + ::ETrackingResult PreviousStatus; }; //----------------------------------------------------------------------------- @@ -96,8 +105,8 @@ qMRMLVirtualRealityTransformWidgetPrivate::qMRMLVirtualRealityTransformWidgetPri : q_ptr(&object) , VRViewNode(nullptr) , TransformNode(nullptr) - , TransformType(vr::TrackedDeviceClass_Invalid) - , PreviousStatus(vr::TrackingResult_Uninitialized) + , TransformType(vtkEventDataDevice::Unknown) + , PreviousStatus(::TrackingResult_Uninitialized) { } @@ -160,26 +169,26 @@ void qMRMLVirtualRealityTransformWidget::setMRMLLinearTransformNode(vtkMRMLLinea if (name.find("VirtualReality") != std::string::npos && name.find("Controller") != std::string::npos) { // It's a controller! - d->TransformType = vr::TrackedDeviceClass_Controller; + d->TransformType = vtkEventDataDevice::LeftController; // Used for both Left and Right } else { if (name.find("VirtualReality") != std::string::npos && name.find("HMD") != std::string::npos) { // It's a headset! - d->TransformType = vr::TrackedDeviceClass_HMD; + d->TransformType = vtkEventDataDevice::HeadMountedDisplay; } else { if (name.find("VirtualReality") != std::string::npos && name.find("GenericTracker") != std::string::npos) { // It's a generic tracker! - d->TransformType = vr::TrackedDeviceClass_GenericTracker; + d->TransformType = vtkEventDataDevice::GenericTracker; } } } - if (d->TransformType == vr::TrackedDeviceClass_Invalid) + if (d->TransformType == vtkEventDataDevice::Unknown) { qCritical() << "Non-VR transform sent to VRTransformWidget"; } @@ -201,18 +210,20 @@ void qMRMLVirtualRealityTransformWidget::onButtonClicked() switch (d->TransformType) { - case vr::TrackedDeviceClass_HMD: + case vtkEventDataDevice::HeadMountedDisplay: d->VRViewNode->SetHMDTransformUpdate(!d->VRViewNode->GetHMDTransformUpdate()); break; - case vr::TrackedDeviceClass_GenericTracker: + case vtkEventDataDevice::GenericTracker: d->VRViewNode->SetTrackerTransformUpdate(!d->VRViewNode->GetTrackerTransformUpdate()); break; - case vr::TrackedDeviceClass_Controller: + case vtkEventDataDevice::LeftController: + Q_FALLTHROUGH(); + case vtkEventDataDevice::RightController: d->VRViewNode->SetControllerTransformsUpdate(!d->VRViewNode->GetControllerTransformsUpdate()); break; default: qCritical() << Q_FUNC_INFO << ": Unable to handle controller button click due to unknown transform type:" - << d->TransformType; + << static_cast(d->TransformType); return; } @@ -224,7 +235,7 @@ void qMRMLVirtualRealityTransformWidget::updateWidgetFromMRML() { Q_D(qMRMLVirtualRealityTransformWidget); - if (d->TransformType == vr::TrackedDeviceClass_Invalid || + if (d->TransformType == vtkEventDataDevice::Unknown || d->TransformNode == nullptr) { d->pushButton_Transform->setEnabled(false); @@ -238,7 +249,7 @@ void qMRMLVirtualRealityTransformWidget::updateWidgetFromMRML() d->pushButton_Transform->setEnabled(true); } - vr::ETrackingResult status = StringToPoseStatus(d->TransformNode->GetAttribute("VirtualReality.PoseStatus")); + ::ETrackingResult status = StringToPoseStatus(d->TransformNode->GetAttribute("VirtualReality.PoseStatus")); if (d->PreviousStatus == status) { // No change @@ -253,7 +264,7 @@ void qMRMLVirtualRealityTransformWidget::updateWidgetFromMRML() // Choose correct icon based on node attributes switch (d->TransformType) { - case vr::TrackedDeviceClass_HMD: + case vtkEventDataDevice::HeadMountedDisplay: { if (!d->VRViewNode->GetHMDTransformUpdate()) { @@ -262,19 +273,19 @@ void qMRMLVirtualRealityTransformWidget::updateWidgetFromMRML() } switch (status) { - case vr::TrackingResult_Running_OK: + case ::TrackingResult_Running_OK: d->pushButton_Transform->setIcon(QIcon(":/Icons/headset_status_ready.png")); break; - case vr::TrackingResult_Running_OutOfRange: + case ::TrackingResult_Running_OutOfRange: d->pushButton_Transform->setIcon(QIcon(":/Icons/headset_status_range.png")); break; - case vr::TrackingResult_Calibrating_InProgress: + case ::TrackingResult_Calibrating_InProgress: d->pushButton_Transform->setIcon(QIcon(":/Icons/headset_status_alert.png")); break; - case vr::TrackingResult_Calibrating_OutOfRange: + case ::TrackingResult_Calibrating_OutOfRange: d->pushButton_Transform->setIcon(QIcon(":/Icons/headset_status_range.png")); break; default: @@ -282,7 +293,7 @@ void qMRMLVirtualRealityTransformWidget::updateWidgetFromMRML() } break; } - case vr::TrackedDeviceClass_GenericTracker: + case vtkEventDataDevice::GenericTracker: { if (!d->VRViewNode->GetTrackerTransformUpdate()) { @@ -291,19 +302,19 @@ void qMRMLVirtualRealityTransformWidget::updateWidgetFromMRML() } switch (status) { - case vr::TrackingResult_Running_OK: + case ::TrackingResult_Running_OK: d->pushButton_Transform->setIcon(QIcon(":/Icons/tracker_status_ready.png")); break; - case vr::TrackingResult_Running_OutOfRange: + case ::TrackingResult_Running_OutOfRange: d->pushButton_Transform->setIcon(QIcon(":/Icons/tracker_status_range.png")); break; - case vr::TrackingResult_Calibrating_InProgress: + case ::TrackingResult_Calibrating_InProgress: d->pushButton_Transform->setIcon(QIcon(":/Icons/tracker_status_alert.png")); break; - case vr::TrackingResult_Calibrating_OutOfRange: + case ::TrackingResult_Calibrating_OutOfRange: d->pushButton_Transform->setIcon(QIcon(":/Icons/tracker_status_range.png")); break; @@ -313,7 +324,9 @@ void qMRMLVirtualRealityTransformWidget::updateWidgetFromMRML() } break; } - case vr::TrackedDeviceClass_Controller: + case vtkEventDataDevice::LeftController: + Q_FALLTHROUGH(); + case vtkEventDataDevice::RightController: { if (!d->VRViewNode->GetControllerTransformsUpdate()) { @@ -322,19 +335,19 @@ void qMRMLVirtualRealityTransformWidget::updateWidgetFromMRML() } switch (status) { - case vr::TrackingResult_Running_OK: + case ::TrackingResult_Running_OK: d->pushButton_Transform->setIcon(QIcon(":/Icons/controller_status_ready.png")); break; - case vr::TrackingResult_Running_OutOfRange: + case ::TrackingResult_Running_OutOfRange: d->pushButton_Transform->setIcon(QIcon(":/Icons/controller_status_range.png")); break; - case vr::TrackingResult_Calibrating_InProgress: + case ::TrackingResult_Calibrating_InProgress: d->pushButton_Transform->setIcon(QIcon(":/Icons/controller_status_alert.png")); break; - case vr::TrackingResult_Calibrating_OutOfRange: + case ::TrackingResult_Calibrating_OutOfRange: d->pushButton_Transform->setIcon(QIcon(":/Icons/controller_status_range.png")); break; @@ -346,7 +359,7 @@ void qMRMLVirtualRealityTransformWidget::updateWidgetFromMRML() break; default: qCritical() << Q_FUNC_INFO << ": Unable to update transform widget due to unknown transform type:" - << d->TransformType; + << static_cast(d->TransformType); break; } } diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityTransformWidget.h b/VirtualReality/Widgets/qMRMLVirtualRealityTransformWidget.h index 7fa09fa..1e51cdf 100644 --- a/VirtualReality/Widgets/qMRMLVirtualRealityTransformWidget.h +++ b/VirtualReality/Widgets/qMRMLVirtualRealityTransformWidget.h @@ -21,21 +21,22 @@ #ifndef __qMRMLVirtualRealityTransformWidget_h #define __qMRMLVirtualRealityTransformWidget_h -// CTK includes -#include -#include - -// SlicerQt includes -#include "qMRMLWidget.h" +// VR MRML includes +class vtkMRMLVirtualRealityViewNode; +// VR Widgets includes #include "qSlicerVirtualRealityModuleWidgetsExport.h" - class qMRMLVirtualRealityTransformWidgetPrivate; + +// CTK includes +#include + +// MRML includes +#include class vtkMRMLLinearTransformNode; class vtkMRMLNode; -class vtkMRMLVirtualRealityViewNode; -/// \ingroup Slicer_QtModules_Markups + class Q_SLICER_QTMODULES_VIRTUALREALITY_WIDGETS_EXPORT qMRMLVirtualRealityTransformWidget : public qMRMLWidget { diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityView.cxx b/VirtualReality/Widgets/qMRMLVirtualRealityView.cxx index 38e32f2..632a895 100644 --- a/VirtualReality/Widgets/qMRMLVirtualRealityView.cxx +++ b/VirtualReality/Widgets/qMRMLVirtualRealityView.cxx @@ -18,17 +18,30 @@ ==============================================================================*/ -// Need to be included before qMRMLVRView_p -#include -#include -#include -//#include //TODO: For debugging the original interactor -#include -//#include //TODO: For debugging the original interactor -#include -#include -#include +// For: +// - SlicerVirtualReality_HAS_OPENVR_SUPPORT +// - SlicerVirtualReality_HAS_OPENXR_SUPPORT +#include "vtkMRMLVirtualRealityConfigure.h" + +// VR Logic includes +#include "vtkSlicerVirtualRealityLogic.h" +// VR MRML includes +#include "vtkMRMLVirtualRealityViewNode.h" + +// VR MRMLDM includes +#include "vtkVirtualRealityViewInteractorObserver.h" +#include "vtkVirtualRealityViewInteractorStyleDelegate.h" +#if defined(SlicerVirtualReality_HAS_OPENVR_SUPPORT) +#include "vtkVirtualRealityViewOpenVRInteractor.h" +#include "vtkVirtualRealityViewOpenVRInteractorStyle.h" +#endif +#if defined(SlicerVirtualReality_HAS_OPENXR_SUPPORT) +#include "vtkVirtualRealityViewOpenXRInteractor.h" +#include "vtkVirtualRealityViewOpenXRInteractorStyle.h" +#endif + +// VR Widgets includes #include "qMRMLVirtualRealityView_p.h" // Qt includes @@ -39,32 +52,52 @@ #include #include #include +#include #include #include // CTK includes -#include -#include +#include // For CTK_GET_CPP, CTK_SET_CPP // Slicer includes -#include "qSlicerApplication.h" -#include "vtkSlicerCamerasModuleLogic.h" - -// VirtualReality includes -#include "vtkMRMLVirtualRealityViewNode.h" +#include +#include // MRMLDisplayableManager includes #include #include #include -#include - // MRML includes #include #include #include +#if defined(SlicerVirtualReality_HAS_OPENVR_SUPPORT) +// VTK Rendering/OpenVR includes +#include +#include +#include +#include + +// OpenVR includes +#include +#endif + +#if defined(SlicerVirtualReality_HAS_OPENXR_SUPPORT) +// VTK Rendering/OpenXR includes +#include +#include +#include +#include +#endif + +// VTK Rendering/VR includes +#include +#include +#include +#include + // VTK includes #include #include @@ -81,6 +114,7 @@ namespace { +#if defined(SlicerVirtualReality_HAS_OPENVR_SUPPORT) //-------------------------------------------------------------------------- std::string PoseStatusToString(vr::ETrackingResult result) { @@ -99,6 +133,7 @@ namespace return "Uninitialized"; } } +#endif } //-------------------------------------------------------------------------- @@ -128,19 +163,47 @@ CTK_SET_CPP(qMRMLVirtualRealityView, vtkSlicerCamerasModuleLogic*, setCamerasLog CTK_GET_CPP(qMRMLVirtualRealityView, vtkSlicerCamerasModuleLogic*, camerasLogic, CamerasLogic); //---------------------------------------------------------------------------- -CTK_GET_CPP(qMRMLVirtualRealityView, vtkOpenVRRenderer*, renderer, Renderer); +CTK_SET_CPP(qMRMLVirtualRealityView, vtkSlicerVirtualRealityLogic*, setVirtualRealityLogic, VirtualRealityLogic); +CTK_GET_CPP(qMRMLVirtualRealityView, vtkSlicerVirtualRealityLogic*, virtualRealityLogic, VirtualRealityLogic); //---------------------------------------------------------------------------- -CTK_GET_CPP(qMRMLVirtualRealityView, vtkOpenVRRenderWindow*, renderWindow, RenderWindow); +CTK_GET_CPP(qMRMLVirtualRealityView, vtkVRRenderer*, renderer, Renderer); //---------------------------------------------------------------------------- -CTK_GET_CPP(qMRMLVirtualRealityView, vtkOpenVRRenderWindowInteractor*, interactor, Interactor); +CTK_GET_CPP(qMRMLVirtualRealityView, vtkVRRenderWindow*, renderWindow, RenderWindow); + +//---------------------------------------------------------------------------- +CTK_GET_CPP(qMRMLVirtualRealityView, vtkVRRenderWindowInteractor*, interactor, Interactor); + +// -------------------------------------------------------------------------- +int qMRMLVirtualRealityView::currentXRRuntime() const +{ + Q_D(const qMRMLVirtualRealityView); + return static_cast(d->currentXRRuntime()); +} //--------------------------------------------------------------------------- -void qMRMLVirtualRealityViewPrivate::createRenderWindow() +void qMRMLVirtualRealityViewPrivate::createRenderWindow(vtkMRMLVirtualRealityViewNode::XRRuntimeType xrRuntime) { Q_Q(qMRMLVirtualRealityView); + if (this->MRMLVirtualRealityViewNode == nullptr) + { + qCritical() << Q_FUNC_INFO << " failed: MRMLVirtualRealityViewNode is not set"; + return; + } + if (this->VirtualRealityLogic == nullptr) + { + qCritical() << Q_FUNC_INFO << " failed: VirtualRealityLogic is not set"; + return; + } + vtkSlicerApplicationLogic* appLogic = qSlicerApplication::application()->applicationLogic(); + if (!appLogic) + { + qCritical() << Q_FUNC_INFO << " failed: Application logic not available"; + return; + } + this->LastViewUpdateTime = vtkSmartPointer::New(); this->LastViewUpdateTime->StartTimer(); this->LastViewUpdateTime->StopTimer(); @@ -154,44 +217,82 @@ void qMRMLVirtualRealityViewPrivate::createRenderWindow() this->LastViewPosition[1] = 0.0; this->LastViewPosition[2] = 0.0; - this->RenderWindow = vtkSmartPointer::New(); - this->Renderer = vtkSmartPointer::New(); - this->Interactor = vtkSmartPointer::New(); - this->Interactor->SetActionManifestDirectory(q->actionManifestPath().toStdString()); - //this->Interactor = vtkSmartPointer::New(); //TODO: For debugging the original interactor - this->InteractorStyle = vtkSmartPointer::New(); - //this->InteractorStyle = vtkSmartPointer::New(); //TODO: For debugging the original interactor - this->Interactor->SetInteractorStyle(this->InteractorStyle); - this->InteractorStyle->SetInteractor(this->Interactor); + // InteractorStyleDelegate + this->InteractorStyleDelegate = vtkSmartPointer::New(); + + // XRRuntime +#if defined(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + if (xrRuntime == vtkMRMLVirtualRealityViewNode::OpenVR) + { + vtkNew interactorStyle; + interactorStyle->SetInteractorStyleDelegate(this->InteractorStyleDelegate); + + this->RenderWindow = vtkSmartPointer::New(); + this->Renderer = vtkSmartPointer::New(); + this->InteractorStyle = interactorStyle; + this->Interactor = vtkSmartPointer::New(); + this->Camera = vtkSmartPointer::New(); + } + else +#endif +#if defined(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + if (xrRuntime == vtkMRMLVirtualRealityViewNode::OpenXR) + { + vtkNew interactorStyle; + interactorStyle->SetInteractorStyleDelegate(this->InteractorStyleDelegate); + + this->RenderWindow = vtkSmartPointer::New(); + this->Renderer = vtkSmartPointer::New(); + this->InteractorStyle = interactorStyle; + this->Interactor = vtkSmartPointer::New(); + this->Camera = vtkSmartPointer::New(); + } + else +#endif + { + this->destroyRenderWindow(); + qWarning() << "No XR runtime initialized"; + this->MRMLVirtualRealityViewNode->SetError("Connection failed: No XR runtime initialized"); + return; + } + + // InteractorStyle this->InteractorStyle->SetCurrentRenderer(this->Renderer); + // Interactor + this->Interactor->SetActionManifestDirectory(this->VirtualRealityLogic->ComputeActionManifestPath(xrRuntime)); + this->Interactor->SetInteractorStyle(this->InteractorStyle); + + // InteractorObserver this->InteractorObserver = vtkVirtualRealityViewInteractorObserver::New(); this->InteractorObserver->SetInteractor(this->Interactor); - this->Camera = vtkSmartPointer::New(); + // Camera this->Renderer->SetActiveCamera(this->Camera); + // RenderWindow this->RenderWindow->SetMultiSamples(0); this->RenderWindow->AddRenderer(this->Renderer); this->RenderWindow->SetInteractor(this->Interactor); // Set default 10x magnification (conversion: PhysicalScale = 1000 / Magnification) this->RenderWindow->SetPhysicalScale(100.0); + + // + // Connections + // + // Observe VR render window event - qvtkReconnect(this->RenderWindow, vtkOpenVRRenderWindow::PhysicalToWorldMatrixModified, + qvtkReconnect(this->RenderWindow, vtkVRRenderWindow::PhysicalToWorldMatrixModified, q, SLOT(onPhysicalToWorldMatrixModified())); // Observe button press event qvtkReconnect(this->Interactor, vtkCommand::Button3DEvent, q, SLOT(onButton3DEvent(vtkObject*,void*,unsigned long,void*))); + // + // DisplayableManager registration + // vtkMRMLVirtualRealityViewDisplayableManagerFactory* factory = vtkMRMLVirtualRealityViewDisplayableManagerFactory::GetInstance(); - - vtkSlicerApplicationLogic* appLogic = qSlicerApplication::application()->applicationLogic(); - if (!appLogic) - { - qCritical() << Q_FUNC_INFO << ": Failed to access application logic"; - return; - } factory->SetMRMLApplicationLogic(appLogic); QStringList displayableManagers; @@ -222,10 +323,12 @@ void qMRMLVirtualRealityViewPrivate::createRenderWindow() this->DisplayableManagerGroup = vtkSmartPointer::Take( factory->InstantiateDisplayableManagers(q->renderer())); this->DisplayableManagerGroup->SetMRMLDisplayableNode(this->MRMLVirtualRealityViewNode); - this->InteractorStyle->SetDisplayableManagers(this->DisplayableManagerGroup); + this->InteractorStyleDelegate->SetDisplayableManagers(this->DisplayableManagerGroup); this->InteractorObserver->SetDisplayableManagers(this->DisplayableManagerGroup); - qDebug() << Q_FUNC_INFO << ": Number of registered displayable manager:" << this->DisplayableManagerGroup->GetDisplayableManagerCount(); + // Default inputs mapping + vtkSlicerVirtualRealityLogic::SetTriggerButtonFunction( + this->Interactor, vtkSlicerVirtualRealityLogic::GetButtonFunctionIdForGrabObjectsAndWorld()); ///CONFIGURATION OF THE OPENVR ENVIRONEMENT @@ -267,12 +370,30 @@ void qMRMLVirtualRealityViewPrivate::createRenderWindow() q->updateViewFromReferenceViewCamera(); + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); this->RenderWindow->Initialize(); - if (!this->RenderWindow->GetHMD()) + QApplication::restoreOverrideCursor(); + + const char* xrRuntimeAsStr = vtkMRMLVirtualRealityViewNode::GetXRRuntimeAsString(xrRuntime); + + if (!this->RenderWindow->GetVRInitialized()) { - qWarning() << Q_FUNC_INFO << ": Failed to initialize OpenVR RenderWindow"; + this->MRMLVirtualRealityViewNode->SetError("Connection failed"); + qWarning() << Q_FUNC_INFO << ": Failed to initialize " << xrRuntimeAsStr << " RenderWindow"; return; } + + qDebug() << ""; + qDebug() << "XR runtime \"" << xrRuntimeAsStr << "\" initialized"; + qDebug() << ""; + qDebug() << "ActionManifestPath:" << q->actionManifestPath(); + qDebug() << "Number of registered displayable manager:" << this->DisplayableManagerGroup->GetDisplayableManagerCount(); + qDebug() << "Registered displayable managers:"; + for (int idx=0; idx < this->DisplayableManagerGroup->GetDisplayableManagerCount(); idx++) + { + qDebug() << " " << this->DisplayableManagerGroup->GetNthDisplayableManager(idx)->GetClassName(); + } + } //--------------------------------------------------------------------------- @@ -282,7 +403,10 @@ void qMRMLVirtualRealityViewPrivate::destroyRenderWindow() // Must break the connection between interactor and render window, // otherwise they would circularly refer to each other and would not // be deleted. - this->Interactor->SetRenderWindow(nullptr); + if (this->Interactor != nullptr) + { + this->Interactor->SetRenderWindow(nullptr); + } this->Interactor = nullptr; this->InteractorStyle = nullptr; this->DisplayableManagerGroup = nullptr; @@ -293,33 +417,97 @@ void qMRMLVirtualRealityViewPrivate::destroyRenderWindow() } // -------------------------------------------------------------------------- -void qMRMLVirtualRealityViewPrivate::updateWidgetFromMRML() +vtkMRMLVirtualRealityViewNode::XRRuntimeType qMRMLVirtualRealityViewPrivate::currentXRRuntime() const { - Q_Q(qMRMLVirtualRealityView); - if (!this->MRMLVirtualRealityViewNode || !this->MRMLVirtualRealityViewNode->GetVisibility()) + if (this->RenderWindow == nullptr) { - if (this->RenderWindow != nullptr) - { - this->destroyRenderWindow(); - } - if (this->MRMLVirtualRealityViewNode) + return vtkMRMLVirtualRealityViewNode::UndefinedXRRuntime; + } +#if defined(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + if (vtkOpenVRRenderWindow::SafeDownCast(this->RenderWindow) != nullptr) + { + return vtkMRMLVirtualRealityViewNode::OpenVR; + } +#endif +#if defined(SlicerVirtualReality_HAS_OPENXR_SUPPORT) + if (vtkOpenXRRenderWindow::SafeDownCast(this->RenderWindow) != nullptr) + { + return vtkMRMLVirtualRealityViewNode::OpenXR; + } +#endif + qCritical() << Q_FUNC_INFO << " failed: RenderWindow is not a supported type: " + << this->RenderWindow->GetClassName(); + return vtkMRMLVirtualRealityViewNode::UndefinedXRRuntime; +} + +// -------------------------------------------------------------------------- +void qMRMLVirtualRealityViewPrivate::updateWidgetFromMRML() +{ + if (this->IsUpdatingWidgetFromMRML) { - this->MRMLVirtualRealityViewNode->ClearError(); + // Updating widget from MRML is already in progress + return; } + this->IsUpdatingWidgetFromMRML = true; + + this->updateWidgetFromMRMLNoModify(); + + this->IsUpdatingWidgetFromMRML = false; +} + +// -------------------------------------------------------------------------- +void qMRMLVirtualRealityViewPrivate::updateWidgetFromMRMLNoModify() +{ + // Finalize XR runtime if the view node is not present or visibility is turned off + // (i.e., disconnected from hardware) + if (!this->MRMLVirtualRealityViewNode || !this->MRMLVirtualRealityViewNode->GetVisibility()) + { + this->destroyRenderWindow(); + this->InitializationAttempts = 0; + this->MRMLVirtualRealityViewNode->ClearError(); return; } - if (!this->RenderWindow) + // Reset initialization attempts and clear errors if the XR runtime has changed + if (this->currentXRRuntime() != this->MRMLVirtualRealityViewNode->GetXRRuntime()) + { + this->InitializationAttempts = 0; + this->MRMLVirtualRealityViewNode->ClearError(); + } + + // Attempt to initialize XR runtime if the current runtime differs or is undefined, and + // the view is visible (i.e., connected to the hardware) + if ((this->currentXRRuntime() != this->MRMLVirtualRealityViewNode->GetXRRuntime() + || this->currentXRRuntime() == vtkMRMLVirtualRealityViewNode::UndefinedXRRuntime) + && this->MRMLVirtualRealityViewNode->GetVisibility()) { - QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); - this->createRenderWindow(); - QApplication::restoreOverrideCursor(); - if (!q->isHardwareConnected()) + // Bail if there are no more attempts left + constexpr int maximumNumberOfAttempts = 1; + if (this->InitializationAttempts >= maximumNumberOfAttempts) { - this->MRMLVirtualRealityViewNode->SetError("Connection failed"); return; } + this->InitializationAttempts++; this->MRMLVirtualRealityViewNode->ClearError(); + + vtkMRMLVirtualRealityViewNode::XRRuntimeType xrRuntime = this->MRMLVirtualRealityViewNode->GetXRRuntime(); + const char* xrRuntimeAsStr = vtkMRMLVirtualRealityViewNode::GetXRRuntimeAsString(xrRuntime); + + // Log the initialization attempt + qDebug().noquote().nospace() + << "Initializing \"" << xrRuntimeAsStr << "\" XR runtime " + << QString("(%1/%2)").arg(this->InitializationAttempts).arg(maximumNumberOfAttempts); + + // Destroy and recreate the render window + this->destroyRenderWindow(); + this->createRenderWindow(xrRuntime); + } + + // Skip further updates if the XR runtime is undefined or if the view node has an error + if (this->currentXRRuntime() == vtkMRMLVirtualRealityViewNode::UndefinedXRRuntime + || this->MRMLVirtualRealityViewNode->HasError()) + { + return; } if (this->DisplayableManagerGroup->GetMRMLDisplayableNode() != this->MRMLVirtualRealityViewNode.GetPointer()) @@ -360,7 +548,7 @@ void qMRMLVirtualRealityViewPrivate::updateWidgetFromMRML() { magnification = 100.0; } - this->InteractorStyle->SetMagnification(magnification); + this->InteractorStyleDelegate->SetMagnification(magnification); // Dolly physical speed double dollyPhysicalSpeedMps = this ->MRMLVirtualRealityViewNode->GetMotionSpeed(); @@ -368,12 +556,14 @@ void qMRMLVirtualRealityViewPrivate::updateWidgetFromMRML() // 1.6666 m/s is walking speed (= 6 km/h), which is the default. We multiply it with the factor this->InteractorStyle->SetDollyPhysicalSpeed(dollyPhysicalSpeedMps); - if (this->RenderWindow->GetHMD()) +#if defined(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + vtkOpenVRRenderWindow* vrRenderWindow = vtkOpenVRRenderWindow::SafeDownCast(this->RenderWindow); + if (vrRenderWindow != nullptr && vrRenderWindow->GetHMD() != nullptr) { vtkEventDataDevice deviceIdsToUpdate[] = { vtkEventDataDevice::RightController, vtkEventDataDevice::LeftController, vtkEventDataDevice::Unknown }; for (int deviceIdIndex = 0; deviceIdsToUpdate[deviceIdIndex] != vtkEventDataDevice::Unknown; deviceIdIndex++) { - vtkOpenVRModel* model = vtkOpenVRModel::SafeDownCast(this->RenderWindow->GetModelForDevice(deviceIdsToUpdate[deviceIdIndex])); + vtkVRModel* model = vtkVRModel::SafeDownCast(vrRenderWindow->GetModelForDevice(deviceIdsToUpdate[deviceIdIndex])); if (!model) { continue; @@ -384,10 +574,10 @@ void qMRMLVirtualRealityViewPrivate::updateWidgetFromMRML() // Update tracking reference visibility for (uint32_t deviceIdIndex = 0; deviceIdIndex < vr::k_unMaxTrackedDeviceCount; ++deviceIdIndex) { - if (this->RenderWindow->GetHMD()->GetTrackedDeviceClass(deviceIdIndex) == vr::TrackedDeviceClass_TrackingReference) + if (vrRenderWindow->GetHMD()->GetTrackedDeviceClass(deviceIdIndex) == vr::TrackedDeviceClass_TrackingReference) { - vtkOpenVRModel* model = vtkOpenVRModel::SafeDownCast(this->RenderWindow->GetModelForDevice( - this->RenderWindow->GetDeviceForOpenVRHandle(deviceIdIndex))); + vtkVRModel* model = vtkVRModel::SafeDownCast(vrRenderWindow->GetModelForDevice( + vrRenderWindow->GetDeviceForOpenVRHandle(deviceIdIndex))); if (!model) { continue; @@ -396,6 +586,7 @@ void qMRMLVirtualRealityViewPrivate::updateWidgetFromMRML() } } } +#endif } if (this->MRMLVirtualRealityViewNode->GetActive()) @@ -432,54 +623,55 @@ double qMRMLVirtualRealityViewPrivate::stillUpdateRate() // -------------------------------------------------------------------------- void qMRMLVirtualRealityViewPrivate::doOpenVirtualReality() { - if (this->Interactor && this->RenderWindow && this->RenderWindow->GetHMD() && this->Renderer) + if (this->Interactor == nullptr) + { + qCritical() << Q_FUNC_INFO << "failed: Interactor is not set"; + return; + } + if (this->Renderer == nullptr) + { + qCritical() << Q_FUNC_INFO << "failed: Renderer is not set"; + return; + } + if (this->RenderWindow == nullptr) + { + qCritical() << Q_FUNC_INFO << "failed: RenderWindow is not set"; + return; + } + + bool hmdConnected = true; +#if defined(SlicerVirtualReality_HAS_OPENVR_SUPPORT) + vtkOpenVRRenderWindow* vrRenderWindow = vtkOpenVRRenderWindow::SafeDownCast(this->RenderWindow); + if (vrRenderWindow != nullptr) + { + hmdConnected = vrRenderWindow->GetHMD() != nullptr; + } +#endif + if (this->RenderWindow->GetVRInitialized() && hmdConnected) { this->Interactor->DoOneEvent(this->RenderWindow, this->Renderer); this->LastViewUpdateTime->StopTimer(); if (this->LastViewUpdateTime->GetElapsedTime() > 0.0) { - bool quickViewMotion = true; - - if (this->MRMLVirtualRealityViewNode->GetMotionSensitivity() > 0.999) - { - quickViewMotion = true; - } - else if (this->MRMLVirtualRealityViewNode->GetMotionSensitivity() <= 0.001) - { - quickViewMotion = false; - } - else if (this->LastViewUpdateTime->GetElapsedTime() < 3.0) // don't consider stale measurements - { - // limit scale: - // sensitivity = 0 -> limit = 10.0x - // sensitivity = 50% -> limit = 1.0x - // sensitivity = 100% -> limit = 0.1x - double limitScale = pow(100, 0.5 - this->MRMLVirtualRealityViewNode->GetMotionSensitivity()); - - const double angularSpeedLimitRadiansPerSec = vtkMath::RadiansFromDegrees(5.0 * limitScale); - double viewDirectionChangeSpeed = vtkMath::AngleBetweenVectors(this->LastViewDirection, - this->Camera->GetViewPlaneNormal()) / this->LastViewUpdateTime->GetElapsedTime(); - double viewUpDirectionChangeSpeed = vtkMath::AngleBetweenVectors(this->LastViewUp, - this->Camera->GetViewUp()) / this->LastViewUpdateTime->GetElapsedTime(); - - const double translationSpeedLimitMmPerSec = 100.0 * limitScale; - // Physical scale = 100 if virtual objects are real-world size; <100 if virtual objects are larger - double viewTranslationSpeedMmPerSec = this->RenderWindow->GetPhysicalScale() * 0.01 * - sqrt(vtkMath::Distance2BetweenPoints(this->LastViewPosition, this->Camera->GetPosition())) - / this->LastViewUpdateTime->GetElapsedTime(); - - if (viewDirectionChangeSpeed < angularSpeedLimitRadiansPerSec - && viewUpDirectionChangeSpeed < angularSpeedLimitRadiansPerSec - && viewTranslationSpeedMmPerSec < translationSpeedLimitMmPerSec) - { - quickViewMotion = false; - } - } + bool quickViewMotion = + vtkSlicerVirtualRealityLogic::ShouldConsiderQuickViewMotion( + this->MRMLVirtualRealityViewNode->GetMotionSensitivity(), + this->RenderWindow->GetPhysicalScale(), + this->LastViewUpdateTime->GetElapsedTime(), + // position and orientation since last view update + this->LastViewPosition, + this->LastViewDirection, + this->LastViewUp, + // current view position and orientation + this->Camera->GetPosition(), + this->Camera->GetViewPlaneNormal(), + this->Camera->GetViewUp()); double updateRate = quickViewMotion ? this->desiredUpdateRate() : this->stillUpdateRate(); this->RenderWindow->SetDesiredUpdateRate(updateRate); + // Save current view position and orientation this->Camera->GetViewPlaneNormal(this->LastViewDirection); this->Camera->GetViewUp(this->LastViewUp); this->Camera->GetPosition(this->LastViewPosition); @@ -558,6 +750,13 @@ void qMRMLVirtualRealityViewPrivate::updateTransformNodesWithTrackerPoses() void qMRMLVirtualRealityViewPrivate ::updateTransformNodeAttributesFromDevice(vtkMRMLTransformNode* node, vtkEventDataDevice device, uint32_t index) { +#ifdef SlicerVirtualReality_HAS_OPENVR_SUPPORT + vtkOpenVRRenderWindow* vrRenderWindow = vtkOpenVRRenderWindow::SafeDownCast(this->RenderWindow); + if (vrRenderWindow == nullptr) + { + return; + } + std::string attributePrefix; switch(device) @@ -580,7 +779,7 @@ ::updateTransformNodeAttributesFromDevice(vtkMRMLTransformNode* node, vtkEventDa } vr::TrackedDevicePose_t* tdPose; - this->RenderWindow->GetOpenVRPose(device, index, &tdPose); + vrRenderWindow->GetOpenVRPose(device, index, &tdPose); if (tdPose == nullptr) { auto handle = this->RenderWindow->GetDeviceHandleForDevice(device, index); @@ -617,6 +816,11 @@ ::updateTransformNodeAttributesFromDevice(vtkMRMLTransformNode* node, vtkEventDa bool poseValid = tdPose != nullptr && tdPose->bPoseIsValid; node->SetAttribute("VirtualReality.PoseValid", poseValid ? "True" : "False"); node->SetAttribute("VirtualReality.PoseStatus", tdPose ? PoseStatusToString(tdPose->eTrackingResult).c_str() : "Uninitialized"); +#else + Q_UNUSED(node); + Q_UNUSED(device); + Q_UNUSED(index); +#endif } //---------------------------------------------------------------------------- @@ -728,41 +932,18 @@ void qMRMLVirtualRealityView::getDisplayableManagers(vtkCollection* displayableM } } -//------------------------------------------------------------------------------ -bool qMRMLVirtualRealityView::isHardwareConnected()const -{ - vtkOpenVRRenderWindow* renWin = this->renderWindow(); - if (!renWin) - { - return false; - } - if (!renWin->GetHMD()) - { - return false; - } - // connected successfully - return true; -} - //------------------------------------------------------------------------------ void qMRMLVirtualRealityView::setGrabObjectsEnabled(bool enable) { Q_D(qMRMLVirtualRealityView); - if (enable) - { - d->InteractorStyle->GrabEnabledOn(); - } - else - { - d->InteractorStyle->GrabEnabledOff(); - } + d->InteractorStyleDelegate->SetGrabEnabled(enable); } //------------------------------------------------------------------------------ bool qMRMLVirtualRealityView::isGrabObjectsEnabled() { Q_D(qMRMLVirtualRealityView); - return d->InteractorStyle->GetGrabEnabled() != 0; + return d->InteractorStyleDelegate->GetGrabEnabled(); } //------------------------------------------------------------------------------ @@ -797,40 +978,43 @@ bool qMRMLVirtualRealityView::isDolly3DEnabled() void qMRMLVirtualRealityView::setGestureButtonToTrigger() { Q_D(qMRMLVirtualRealityView); - d->Interactor->SetGestureButtonToTrigger(); + vtkSlicerVirtualRealityLogic::SetGestureButtonToTrigger(d->Interactor); } //------------------------------------------------------------------------------ void qMRMLVirtualRealityView::setGestureButtonToGrip() { Q_D(qMRMLVirtualRealityView); - d->Interactor->SetGestureButtonToGrip(); + vtkSlicerVirtualRealityLogic::SetGestureButtonToGrip(d->Interactor); } //------------------------------------------------------------------------------ void qMRMLVirtualRealityView::setGestureButtonToTriggerAndGrip() { Q_D(qMRMLVirtualRealityView); - d->Interactor->SetGestureButtonToTriggerAndGrip(); + vtkSlicerVirtualRealityLogic::SetGestureButtonToTriggerAndGrip(d->Interactor); } //------------------------------------------------------------------------------ void qMRMLVirtualRealityView::setGestureButtonToNone() { Q_D(qMRMLVirtualRealityView); - d->Interactor->SetGestureButtonToNone(); + vtkSlicerVirtualRealityLogic::SetGestureButtonToNone(d->Interactor); } //------------------------------------------------------------------------------ -CTK_SET_CPP(qMRMLVirtualRealityView, const QString&, setActionManifestPath, ActionManifestPath); -CTK_GET_CPP(qMRMLVirtualRealityView, QString, actionManifestPath, ActionManifestPath); +QString qMRMLVirtualRealityView::actionManifestPath() const +{ + Q_D(const qMRMLVirtualRealityView); + return QString::fromStdString(d->Interactor->GetActionManifestDirectory()); +} //------------------------------------------------------------------------------ void qMRMLVirtualRealityView::onPhysicalToWorldMatrixModified() { Q_D(qMRMLVirtualRealityView); - d->MRMLVirtualRealityViewNode->SetMagnification(d->InteractorStyle->GetMagnification()); + d->MRMLVirtualRealityViewNode->SetMagnification(d->InteractorStyleDelegate->GetMagnification()); emit physicalToWorldMatrixModified(); } @@ -964,7 +1148,7 @@ void qMRMLVirtualRealityView::updateViewFromReferenceViewCamera() qWarning() << Q_FUNC_INFO << ": The renderer must be set prior to calling InitializeViewFromCamera"; return; } - vtkOpenVRCamera* cam = vtkOpenVRCamera::SafeDownCast(ren->GetActiveCamera()); + vtkVRCamera* cam = vtkVRCamera::SafeDownCast(ren->GetActiveCamera()); if (!cam) { qWarning() << Q_FUNC_INFO << ": The renderer's active camera must be set prior to calling InitializeViewFromCamera"; diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityView.h b/VirtualReality/Widgets/qMRMLVirtualRealityView.h index 12de0e2..8e99f5e 100644 --- a/VirtualReality/Widgets/qMRMLVirtualRealityView.h +++ b/VirtualReality/Widgets/qMRMLVirtualRealityView.h @@ -21,30 +21,39 @@ #ifndef __qMRMLVirtualRealityView_h #define __qMRMLVirtualRealityView_h -// CTK includes -#include -#include +// VR Logic includes +class vtkSlicerVirtualRealityLogic; -// Qt includes -#include +// VR MRML includes +class vtkMRMLVirtualRealityViewNode; + +// VR MRMLDM includes +class vtkVirtualRealityViewInteractorObserver; +// VR Widgets includes #include "qSlicerVirtualRealityModuleWidgetsExport.h" +class qMRMLVirtualRealityViewPrivate; + +// Qt includes +#include +#include // CTK includes -#include +#include -class qMRMLVirtualRealityViewPrivate; -class vtkMRMLVirtualRealityViewNode; +// Slicer includes +class vtkSlicerCamerasModuleLogic; + +// VTK Rendering/VR includes +class vtkVRRenderer; +class vtkVRRenderWindow; +class vtkVRRenderWindowInteractor; + +// VTK includes class vtkCollection; class vtkGenericOpenGLRenderWindow; +class vtkObject; class vtkRenderWindowInteractor; -class vtkSlicerCamerasModuleLogic; -class vtkVirtualRealityViewInteractorObserver; - -class vtkOpenVRRenderer; -class vtkOpenVRRenderWindow; -class vtkOpenVRRenderWindowInteractor; -class vtkOpenVRCamera; /// \brief 3D view for view nodes. /// For performance reasons, the view block refreshes when the scene is in @@ -80,30 +89,47 @@ class Q_SLICER_QTMODULES_VIRTUALREALITY_WIDGETS_EXPORT qMRMLVirtualRealityView : void addDisplayableManager(const QString& displayableManager); Q_INVOKABLE void getDisplayableManagers(vtkCollection *displayableManagers); + ///@{ /// Set Cameras module logic. /// Required for updating camera from reference view node. void setCamerasLogic(vtkSlicerCamerasModuleLogic* camerasLogic); vtkSlicerCamerasModuleLogic* camerasLogic()const; + ///}@ + + ///@{ + /// VirtualReality module logic. + void setVirtualRealityLogic(vtkSlicerVirtualRealityLogic* camerasLogic); + vtkSlicerVirtualRealityLogic* virtualRealityLogic()const; + ///}@ /// Get the 3D View node observed by view. Q_INVOKABLE vtkMRMLVirtualRealityViewNode* mrmlVirtualRealityViewNode()const; /// Get a reference to the associated vtkRenderer - vtkOpenVRRenderer* renderer()const; + vtkVRRenderer* renderer()const; /// Get underlying RenderWindow - Q_INVOKABLE vtkOpenVRRenderWindow* renderWindow()const; + Q_INVOKABLE vtkVRRenderWindow* renderWindow()const; + + /// Get the current XR runtime + /// + /// The runtime is determined from the instance of vtkVRRenderWindow associated + /// with this view. + /// + /// \warning The return type has been deliberately changed from `vtkMRMLVirtualRealityViewNode::XRRuntimeType` + /// to `int` to enable invocation from Python. Attempts to register the enum using both Q_DECLARE_METATYPE + /// and `qRegisterMetaType()` resulted in a runtime segfault. + /// + /// \sa renderWindow() + Q_INVOKABLE int currentXRRuntime() const; /// Get underlying RenderWindow - Q_INVOKABLE vtkOpenVRRenderWindowInteractor* interactor()const; + Q_INVOKABLE vtkVRRenderWindowInteractor* interactor()const; /// Initialize the virtual reality view to most closely /// matched the camera of the reference view camera. Q_INVOKABLE void updateViewFromReferenceViewCamera(); - /// Get underlying RenderWindow - Q_INVOKABLE bool isHardwareConnected()const; - /// Enable/disable grabbing and moving objects in the scene Q_INVOKABLE void setGrabObjectsEnabled(bool enable); /// Get whether grabbing and moving objects in the scene is enabled @@ -127,7 +153,6 @@ class Q_SLICER_QTMODULES_VIRTUALREALITY_WIDGETS_EXPORT qMRMLVirtualRealityView : ///@{ /// Path where the action manifest .json files are located. - Q_INVOKABLE void setActionManifestPath(const QString& path); Q_INVOKABLE QString actionManifestPath() const; ///@} diff --git a/VirtualReality/Widgets/qMRMLVirtualRealityView_p.h b/VirtualReality/Widgets/qMRMLVirtualRealityView_p.h index 257ea7e..a2f52b5 100644 --- a/VirtualReality/Widgets/qMRMLVirtualRealityView_p.h +++ b/VirtualReality/Widgets/qMRMLVirtualRealityView_p.h @@ -32,38 +32,42 @@ // We mean it. // -// VTK includes -#include -#include +// VR MRML includes +#include "vtkMRMLVirtualRealityViewNode.h" -// CTK includes -#include -#include +// VR MRMLDM includes +class vtkVirtualRealityViewInteractorStyleDelegate; +class vtkVirtualRealityViewInteractorObserver; -// qMRML includes +// VR Widgets includes #include "qMRMLVirtualRealityView.h" -// Qt includes -#include - -class QLabel; -class vtkLightCollection; -class vtkMRMLCameraNode; +// MRML includes class vtkMRMLDisplayableManagerGroup; class vtkMRMLTransformNode; -class vtkMRMLVirtualRealityViewNode; + +// VTK Rendering/VR includes +class vtkVRCamera; +class vtkVRInteractorStyle; +class vtkVRRenderer; +class vtkVRRenderWindow; +class vtkVRRenderWindowInteractor; + +// VTK includes +#include +#include +#include +class vtkLightCollection; class vtkObject; -class vtkOpenVRInteractorStyle; -class vtkOpenVRRenderWindowInteractor; class vtkTimerLog; -class vtkVirtualRealityViewInteractor; -class vtkVirtualRealityViewInteractorObserver; -class vtkVirtualRealityViewInteractorStyle; -namespace vr -{ - struct TrackedDevicePose_t; -} +// CTK includes +#include + +// Qt includes +#include +#include +#include //----------------------------------------------------------------------------- class qMRMLVirtualRealityViewPrivate: public QObject @@ -85,11 +89,14 @@ class qMRMLVirtualRealityViewPrivate: public QObject double desiredUpdateRate(); double stillUpdateRate(); + vtkMRMLVirtualRealityViewNode::XRRuntimeType currentXRRuntime() const; + public slots: void updateWidgetFromMRML(); void doOpenVirtualReality(); protected: + void updateWidgetFromMRMLNoModify(); void updateTransformNodeWithControllerPose(vtkEventDataDevice device); void updateTransformNodeWithHMDPose(); void updateTransformNodesWithTrackerPoses(); @@ -97,21 +104,24 @@ public slots: void updateTransformNodeFromDevice(vtkMRMLTransformNode* node, vtkEventDataDevice device, uint32_t index=0); void updateTransformNodeAttributesFromDevice(vtkMRMLTransformNode* node, vtkEventDataDevice device, uint32_t index=0); - void createRenderWindow(); + void createRenderWindow(vtkMRMLVirtualRealityViewNode::XRRuntimeType xrRuntime); void destroyRenderWindow(); vtkSlicerCamerasModuleLogic* CamerasLogic; + vtkSmartPointer VirtualRealityLogic; vtkSmartPointer DisplayableManagerGroup; vtkSmartPointer InteractorObserver; + vtkSmartPointer InteractorStyleDelegate; vtkWeakPointer MRMLVirtualRealityViewNode; - vtkSmartPointer Renderer; - vtkSmartPointer RenderWindow; - vtkSmartPointer Interactor; - //vtkSmartPointer Interactor; //TODO: For debugging the original interactor - vtkSmartPointer InteractorStyle; - //vtkSmartPointer InteractorStyle; //TODO: For debugging the original interactor - vtkSmartPointer Camera; + + // XRRuntime + vtkSmartPointer Renderer; + vtkSmartPointer RenderWindow; + vtkSmartPointer Interactor; + vtkSmartPointer InteractorStyle; + vtkSmartPointer Camera; + vtkSmartPointer Lights; vtkSmartPointer LastViewUpdateTime; @@ -121,6 +131,9 @@ public slots: QString ActionManifestPath; + bool IsUpdatingWidgetFromMRML{false}; + int InitializationAttempts{0}; + QTimer VirtualRealityLoopTimer; }; diff --git a/VirtualReality/qSlicerVirtualRealityModule.cxx b/VirtualReality/qSlicerVirtualRealityModule.cxx index d5e0e83..216b133 100644 --- a/VirtualReality/qSlicerVirtualRealityModule.cxx +++ b/VirtualReality/qSlicerVirtualRealityModule.cxx @@ -15,6 +15,19 @@ ==============================================================================*/ +// VR includes +#include "qSlicerVirtualRealityModule.h" +#include "qSlicerVirtualRealityModuleWidget.h" + +// VR Logic includes +#include + +// VR MRML includes +#include + +// VR Widgets includes +#include + // Qt includes #include #include @@ -26,33 +39,24 @@ #include // SubjectHierarchy Plugins includes -#include "qSlicerSubjectHierarchyPluginHandler.h" -#include "qSlicerSubjectHierarchyVirtualRealityPlugin.h" +#include +#include // VirtualReality Widget includes #include -// VirtualReality Logic includes -#include - -// VirtualReality includes -#include "qSlicerVirtualRealityModule.h" -#include "qSlicerVirtualRealityModuleWidget.h" -#include "qMRMLVirtualRealityView.h" -#include "vtkMRMLVirtualRealityViewNode.h" - -// SlicerQt includes -#include "qSlicerApplication.h" -#include "qSlicerCoreApplication.h" -#include "qSlicerModuleManager.h" +// Slicer includes +#include +#include +#include // MRML includes -#include "vtkMRMLScene.h" +#include // Slicer includes -#include "vtkSlicerApplicationLogic.h" -#include "vtkSlicerCamerasModuleLogic.h" -#include "vtkSlicerVolumeRenderingLogic.h" +#include +#include +#include //----------------------------------------------------------------------------- #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0)) @@ -135,40 +139,7 @@ void qSlicerVirtualRealityModulePrivate::addViewWidget() this->VirtualRealityViewWidget = new qMRMLVirtualRealityView(); this->VirtualRealityViewWidget->setObjectName(QString("VirtualRealityWidget")); - if(q->isInstalled()) - { - // "vr_actions" sub-directory is hard-coded in "VTK/Rendering/OpenVR/CMakeLists.txt" - // "Slicer_THIRDPARTY_LIB_DIR" is set in "External_vtkRenderingOpenVR.cmake" - QString actionManifestPath = QString("%1/SlicerVirtualReality/%2/vr_actions/").arg( - qSlicerCoreApplication::application()->extensionsInstallPath(), Slicer_THIRDPARTY_LIB_DIR); - this->VirtualRealityViewWidget->setActionManifestPath(actionManifestPath); - } - else - { - // Since the output of qSlicerAbstractCoreModule::path() is - // - // /qt-loadable-modules[/(Release|Debug|...)] - // - // where - // - // is equivalent to /inner-build/lib/Slicer-X.Y - // - // and the action manifest files are in this directory - // - // /vtkRenderingOpenVR-build/vtkRenderingOpenVR/ - // - // we compose the path as such: - - // First, we retrieve - std::string moduleLibDirectory = vtkSlicerApplicationLogic::GetModuleSlicerXYLibDirectory(q->path().toStdString()); - - // ... then we change the directory to vtkRenderingOpenVR-build/vtkRenderingOpenVR/ - QString actionManifestPath = QString::fromStdString(moduleLibDirectory + "/../../../vtkRenderingOpenVR-build/vtkRenderingOpenVR/"); - - this->VirtualRealityViewWidget->setActionManifestPath(actionManifestPath); - } - - qDebug() << "actionManifestPath:" << this->VirtualRealityViewWidget->actionManifestPath(); + this->VirtualRealityViewWidget->setVirtualRealityLogic(this->logic()); qSlicerAbstractCoreModule* camerasModule = qSlicerCoreApplication::application()->moduleManager()->module("Cameras"); @@ -434,7 +405,9 @@ qSlicerAbstractModuleRepresentation* qSlicerVirtualRealityModule::createWidgetRe //----------------------------------------------------------------------------- vtkMRMLAbstractLogic* qSlicerVirtualRealityModule::createLogic() { - return vtkSlicerVirtualRealityLogic::New(); + vtkSlicerVirtualRealityLogic* logic = vtkSlicerVirtualRealityLogic::New(); + logic->SetModuleInstalled(this->isInstalled()); + return logic;; } //----------------------------------------------------------------------------- diff --git a/VirtualReality/qSlicerVirtualRealityModule.h b/VirtualReality/qSlicerVirtualRealityModule.h index b59323e..29ffb11 100644 --- a/VirtualReality/qSlicerVirtualRealityModule.h +++ b/VirtualReality/qSlicerVirtualRealityModule.h @@ -18,13 +18,15 @@ #ifndef __qSlicerVirtualRealityModule_h #define __qSlicerVirtualRealityModule_h -// SlicerQt includes -#include "qSlicerLoadableModule.h" - -#include "qSlicerVirtualRealityModuleExport.h" +// Slicer includes +#include +// CTK includes #include +// VR includes +#include "qSlicerVirtualRealityModuleExport.h" + class vtkMRMLVirtualRealityViewNode; class qSlicerVirtualRealityModulePrivate; class qMRMLVirtualRealityView; diff --git a/VirtualReality/qSlicerVirtualRealityModuleWidget.cxx b/VirtualReality/qSlicerVirtualRealityModuleWidget.cxx index 504b079..f3ada60 100644 --- a/VirtualReality/qSlicerVirtualRealityModuleWidget.cxx +++ b/VirtualReality/qSlicerVirtualRealityModuleWidget.cxx @@ -18,18 +18,22 @@ // Qt includes #include -// SlicerQt includes +// CTK includes +#include + +// VR includes +#include "qSlicerVirtualRealityModule.h" #include "qSlicerVirtualRealityModuleWidget.h" #include "ui_qSlicerVirtualRealityModuleWidget.h" -// CTK includes -#include "ctkDoubleSpinBox.h" +// VR Logic includes +#include "vtkSlicerVirtualRealityLogic.h" -// VirtualReality includes -#include "qSlicerVirtualRealityModule.h" -#include "qMRMLVirtualRealityView.h" +// VR MRML includes #include "vtkMRMLVirtualRealityViewNode.h" -#include "vtkSlicerVirtualRealityLogic.h" + +// VR widgets includes +#include "qMRMLVirtualRealityView.h" //----------------------------------------------------------------------------- /// \ingroup Slicer_QtModules_ExtensionTemplate @@ -69,6 +73,12 @@ void qSlicerVirtualRealityModuleWidget::setup() d->setupUi(this); this->Superclass::setup(); + for (int xrRuntimeIndex=0; xrRuntimeIndex < vtkMRMLVirtualRealityViewNode::XRRuntime_Last; xrRuntimeIndex++) + { + d->XRRuntimeComboBox->addItem(vtkMRMLVirtualRealityViewNode::GetXRRuntimeAsString(xrRuntimeIndex), xrRuntimeIndex); + } + connect(d->XRRuntimeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(setVirtualRealityXRRuntime(int))); + connect(d->ConnectCheckBox, SIGNAL(toggled(bool)), this, SLOT(setVirtualRealityConnected(bool))); connect(d->RenderingEnabledCheckBox, SIGNAL(toggled(bool)), this, SLOT(setVirtualRealityActive(bool))); connect(d->TwoSidedLightingCheckBox, SIGNAL(toggled(bool)), this, SLOT(setTwoSidedLighting(bool))); @@ -97,6 +107,15 @@ void qSlicerVirtualRealityModuleWidget::setup() qvtkConnect(logic(), vtkCommand::ModifiedEvent, this, SLOT(updateWidgetFromMRML())); } +//-------------------------------------------------------------------------- +namespace +{ +vtkMRMLVirtualRealityViewNode::XRRuntimeType defaultXRRuntime() +{ + return vtkMRMLVirtualRealityViewNode::OpenVR; +} +} + //-------------------------------------------------------------------------- void qSlicerVirtualRealityModuleWidget::updateWidgetFromMRML() { @@ -108,6 +127,12 @@ void qSlicerVirtualRealityModuleWidget::updateWidgetFromMRML() d->ConnectCheckBox->setChecked(vrViewNode != nullptr && vrViewNode->GetVisibility()); d->ConnectCheckBox->blockSignals(wasBlocked); + wasBlocked = d->XRRuntimeComboBox->blockSignals(true); + d->XRRuntimeComboBox->setCurrentIndex( + d->XRRuntimeComboBox->findData( + vrViewNode != nullptr ? vrViewNode->GetXRRuntime() : defaultXRRuntime())); + d->XRRuntimeComboBox->blockSignals(wasBlocked); + QString errorText; if (vrViewNode && vrViewNode->HasError()) { @@ -188,6 +213,14 @@ void qSlicerVirtualRealityModuleWidget::updateWidgetFromMRML() && vrViewNode->GetReferenceViewNode() != nullptr); } + +//----------------------------------------------------------------------------- +void qSlicerVirtualRealityModuleWidget::setVirtualRealityXRRuntime(int index) +{ + vtkSlicerVirtualRealityLogic* vrLogic = vtkSlicerVirtualRealityLogic::SafeDownCast(this->logic()); + vrLogic->SetVirtualRealityXRRuntime(static_cast(index)); +} + //----------------------------------------------------------------------------- void qSlicerVirtualRealityModuleWidget::setVirtualRealityConnected(bool connect) { diff --git a/VirtualReality/qSlicerVirtualRealityModuleWidget.h b/VirtualReality/qSlicerVirtualRealityModuleWidget.h index 2e7133a..5add30f 100644 --- a/VirtualReality/qSlicerVirtualRealityModuleWidget.h +++ b/VirtualReality/qSlicerVirtualRealityModuleWidget.h @@ -18,9 +18,10 @@ #ifndef __qSlicerVirtualRealityModuleWidget_h #define __qSlicerVirtualRealityModuleWidget_h -// SlicerQt includes -#include "qSlicerAbstractModuleWidget.h" +// Slicer includes +#include +// VR includes #include "qSlicerVirtualRealityModuleExport.h" class qSlicerVirtualRealityModuleWidgetPrivate; @@ -37,6 +38,7 @@ class Q_SLICER_QTMODULES_VIRTUALREALITY_EXPORT qSlicerVirtualRealityModuleWidget ~qSlicerVirtualRealityModuleWidget() override; public slots: + void setVirtualRealityXRRuntime(int index); void setVirtualRealityConnected(bool connect); void setVirtualRealityActive(bool activate); void setTwoSidedLighting(bool);