diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e81bb53f..dcc23ebc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -293,6 +293,7 @@ jobs: robocopy .\build${{ matrix.arch }}\rundir\RelWithDebInfo\obs-plugins\${{ matrix.arch }}bit\ .\package\obs-plugins\${{ matrix.arch }}bit ${{ env.LIB_NAME }}* /E /XF .gitignore robocopy .\build${{ matrix.arch }}\rundir\RelWithDebInfo\data\obs-plugins\${{ env.LIB_NAME }}\ .\package\data\obs-plugins\${{ env.LIB_NAME }}\ /E /XF .gitignore cp UI/frontend-plugins/${{ env.PLUGIN_NAME }}/deps/opencv/build/bin/Release/*dll package/obs-plugins/${{ matrix.arch }}bit + cp UI/frontend-plugins/${{ env.PLUGIN_NAME }}/deps/openvr/bin/win${{ matrix.arch }}/*dll package/obs-plugins/${{ matrix.arch }}bit exit 0 - name: Publish zip if: success() diff --git a/.gitmodules b/.gitmodules index fd47bdea..5cf7830c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "deps/opencv"] path = deps/opencv url = https://github.com/opencv/opencv.git +[submodule "deps/openvr"] + path = deps/openvr + url = https://github.com/ValveSoftware/openvr.git diff --git a/CMakeLists.txt b/CMakeLists.txt index c57dcf7a..717c63f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -395,12 +395,8 @@ add_library( # Out of tree build if(BUILD_OUT_OF_TREE) target_link_libraries( - advanced-scene-switcher - ${advanced-scene-switcher_PLATFORM_LIBS} - ${LIBOBS_LIB} - ${LIBOBS_FRONTEND_API_LIB} - Qt5::Core - Qt5::Widgets) + advanced-scene-switcher ${advanced-scene-switcher_PLATFORM_LIBS} + ${LIBOBS_LIB} ${LIBOBS_FRONTEND_API_LIB} Qt5::Core Qt5::Widgets) # Additional commands to install the module in the correct place. Find all the # translation files so we can copy them to the correct place later on. diff --git a/data/locale/en-US.ini b/data/locale/en-US.ini index c2e043e4..d7d4b7aa 100644 --- a/data/locale/en-US.ini +++ b/data/locale/en-US.ini @@ -257,6 +257,11 @@ AdvSceneSwitcher.condition.studioMode.state.active="Studio mode is active" AdvSceneSwitcher.condition.studioMode.state.notActive="Studio mode is not active" AdvSceneSwitcher.condition.studioMode.state.previewScene="Preview scene is" AdvSceneSwitcher.condition.studioMode.entry="{{conditions}}{{scenes}}" +AdvSceneSwitcher.condition.openvr="OpenVR" +AdvSceneSwitcher.condition.errorStatus="OpenVR error: " +AdvSceneSwitcher.condition.openvr.entry.line1="HMD is in ..." +AdvSceneSwitcher.condition.openvr.entry.line2="{{controls}}" +AdvSceneSwitcher.condition.openvr.entry.line3="HMD is currently at {{xPos}} x {{yPos}} x {{zPos}}" ; Macro Actions AdvSceneSwitcher.action.switchScene="Switch scene" diff --git a/deps/openvr b/deps/openvr new file mode 160000 index 00000000..4c85abcb --- /dev/null +++ b/deps/openvr @@ -0,0 +1 @@ +Subproject commit 4c85abcb7f7f1f02adaf3812018c99fc593bc341 diff --git a/src/external-macro-modules/CMakeLists.txt b/src/external-macro-modules/CMakeLists.txt index e013ff23..46a5a0a7 100644 --- a/src/external-macro-modules/CMakeLists.txt +++ b/src/external-macro-modules/CMakeLists.txt @@ -2,3 +2,4 @@ # or other components which might potentially not be fulfilled by the user and # thus cause issues. add_subdirectory(opencv) +add_subdirectory(openvr) diff --git a/src/external-macro-modules/openvr/CMakeLists.txt b/src/external-macro-modules/openvr/CMakeLists.txt new file mode 100644 index 00000000..77b83275 --- /dev/null +++ b/src/external-macro-modules/openvr/CMakeLists.txt @@ -0,0 +1,89 @@ +cmake_minimum_required(VERSION 3.14) +project(advanced-scene-switcher-openvr) + +if(NOT WIN32) + message( + WARNING "OpenVR condition is only supported on Windows builds for now.") + return() +endif(NOT WIN32) + +add_definitions(-DADVSS_MODULE) + +# openvr +if(NOT OpenVR_DIR) + set(OpenVR_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../deps/openvr) +endif() + +if(EXISTS ${OpenVR_DIR}) + set(SIZEOF_VOIDP ${CMAKE_SIZEOF_VOID_P}) + if((NOT APPLE) AND (CMAKE_SIZEOF_VOID_P EQUAL 8)) + set(PROCESSOR_ARCH "64") + else() + set(PROCESSOR_ARCH "32") + endif() + if(WIN32) + set(PLATFORM_NAME "win") + elseif(UNIX AND NOT APPLE) + if(CMAKE_SYSTEM_NAME MATCHES ".*Linux") + set(PLATFORM_NAME "linux") + endif() + elseif(APPLE) + if(CMAKE_SYSTEM_NAME MATCHES ".*Darwin.*" OR CMAKE_SYSTEM_NAME MATCHES + ".*MacOS.*") + set(PLATFORM_NAME "osx") + endif() + endif() + set(OpenVR_INCLUDE_DIRS ${OpenVR_DIR}/headers) + set(OpenVR_BINARIES + ${OpenVR_DIR}/bin/${PLATFORM_NAME}${PROCESSOR_ARCH}/${CMAKE_SHARED_LIBRARY_PREFIX}openvr_api${CMAKE_SHARED_LIBRARY_SUFFIX} + ) + set(OpenVR_LIBRARIES + ${OpenVR_DIR}/lib/${PLATFORM_NAME}${PROCESSOR_ARCH}/${CMAKE_SHARED_LIBRARY_PREFIX}openvr_api${CMAKE_IMPORT_LIBRARY_SUFFIX} + ) + set(OpenVR_FOUND TRUE) +endif() + +if(OpenVR_FOUND) + include_directories("${OpenVR_INCLUDE_DIRS}") +else() + set(OpenVR_LIBRARIES "") + message( + WARNING + "OpenVR not found! Functionality relying on OpenVR will be disabled!\nOpenVR sources are available under: ${CMAKE_CURRENT_SOURCE_DIR}/deps/openvr" + return + ()) +endif() + +include_directories("${CMAKE_CURRENT_SOURCE_DIR}/../../headers") +set(module_SOURCES macro-condition-openvr.cpp macro-condition-openvr.hpp) +add_library(advanced-scene-switcher-openvr MODULE ${module_SOURCES}) + +if(BUILD_OUT_OF_TREE) + target_link_libraries( + advanced-scene-switcher-openvr + advanced-scene-switcher + ${LIBOBS_LIB} + ${LIBOBS_FRONTEND_API_LIB} + ${OpenVR_LIBRARIES} + Qt5::Core + Qt5::Widgets) + + if(UNIX AND NOT APPLE) + if(NOT LIB_OUT_DIR) + set(LIB_OUT_DIR "/lib/obs-plugins") + endif() + set_target_properties(advanced-scene-switcher-openvr PROPERTIES PREFIX "") + install(TARGETS advanced-scene-switcher-openvr + LIBRARY DESTINATION ${CMAKE_INSTALL_PREFIX}/${LIB_OUT_DIR}) + endif() +else() + target_link_libraries( + advanced-scene-switcher-openvr + advanced-scene-switcher + obs-frontend-api + ${OpenVR_LIBRARIES} + Qt5::Core + Qt5::Widgets + libobs) + install_obs_plugin(advanced-scene-switcher-openvr) +endif() diff --git a/src/external-macro-modules/openvr/macro-condition-openvr.cpp b/src/external-macro-modules/openvr/macro-condition-openvr.cpp new file mode 100644 index 00000000..82f86e76 --- /dev/null +++ b/src/external-macro-modules/openvr/macro-condition-openvr.cpp @@ -0,0 +1,297 @@ +#include "macro-condition-openvr.hpp" + +#include +#include +#include +#include + +const std::string MacroConditionOpenVR::id = "openvr"; + +bool MacroConditionOpenVR::_registered = MacroConditionFactory::Register( + MacroConditionOpenVR::id, + {MacroConditionOpenVR::Create, MacroConditionOpenVREdit::Create, + "AdvSceneSwitcher.condition.openvr"}); + +static vr::IVRSystem *openvrSystem; +std::mutex openvrMutex; +std::condition_variable openvrCV; + +void processOpenVREvents() +{ + std::unique_lock lock(openvrMutex); + vr::VREvent_t event; + while (true) { + if (openvrSystem->PollNextEvent(&event, sizeof(event)) && + event.eventType == vr::VREvent_Quit) { + openvrSystem->AcknowledgeQuit_Exiting(); + vr::VR_Shutdown(); + openvrSystem = nullptr; + break; + } + openvrCV.wait_for(lock, std::chrono::milliseconds(100)); + } + blog(LOG_INFO, "stop handling openVR events"); +} + +void initOpenVR(vr::EVRInitError &err) +{ + openvrSystem = vr::VR_Init(&err, vr::VRApplication_Background); + if (openvrSystem) { + // Don't kill OBS if SteamVR is exiting + std::thread t(processOpenVREvents); + t.detach(); + } +} + +struct TrackingData { + float x, y, z; + bool valid = false; +}; + +TrackingData getOpenVRPos(vr::EVRInitError &err) +{ + TrackingData data; + std::unique_lock lock(openvrMutex); + if (!openvrSystem) { + initOpenVR(err); + } + + if (openvrSystem && vr::VRCompositor() && + openvrSystem->IsTrackedDeviceConnected(0)) { + vr::TrackedDevicePose_t poses[vr::k_unMaxTrackedDeviceCount]; + openvrSystem->GetDeviceToAbsoluteTrackingPose( + vr::TrackingUniverseStanding, 0.0, poses, + vr::k_unMaxTrackedDeviceCount); + auto hmdPose = poses[vr::k_unTrackedDeviceIndex_Hmd]; + auto mat = hmdPose.mDeviceToAbsoluteTracking.m; + data.x = mat[0][3]; + data.y = mat[1][3]; + data.z = mat[2][3]; + data.valid = true; + } + return data; +} + +bool MacroConditionOpenVR::CheckCondition() +{ + vr::EVRInitError err; + TrackingData data = getOpenVRPos(err); + if (!data.valid) { + return false; + } + + return data.x >= _minX && data.y >= _minY && data.z >= _minZ && + data.x <= _maxX && data.y <= _maxY && data.z <= _maxZ; +} + +bool MacroConditionOpenVR::Save(obs_data_t *obj) +{ + MacroCondition::Save(obj); + obs_data_set_double(obj, "minX", _minX); + obs_data_set_double(obj, "minY", _minY); + obs_data_set_double(obj, "minZ", _minZ); + obs_data_set_double(obj, "maxX", _maxX); + obs_data_set_double(obj, "maxY", _maxY); + obs_data_set_double(obj, "maxZ", _maxZ); + return true; +} + +bool MacroConditionOpenVR::Load(obs_data_t *obj) +{ + MacroCondition::Load(obj); + _minX = obs_data_get_double(obj, "minX"); + _minY = obs_data_get_double(obj, "minY"); + _minZ = obs_data_get_double(obj, "minZ"); + _maxX = obs_data_get_double(obj, "maxX"); + _maxY = obs_data_get_double(obj, "maxY"); + _maxZ = obs_data_get_double(obj, "maxZ"); + return true; +} + +MacroConditionOpenVREdit::MacroConditionOpenVREdit( + QWidget *parent, std::shared_ptr entryData) + : QWidget(parent) +{ + _minX = new QDoubleSpinBox(); + _minY = new QDoubleSpinBox(); + _minZ = new QDoubleSpinBox(); + _maxX = new QDoubleSpinBox(); + _maxY = new QDoubleSpinBox(); + _maxZ = new QDoubleSpinBox(); + _xPos = new QLabel("-"); + _yPos = new QLabel("-"); + _zPos = new QLabel("-"); + _errLabel = new QLabel(); + _errLabel->setVisible(false); + + _minX->setPrefix("Min X: "); + _minY->setPrefix("Min Y: "); + _minZ->setPrefix("Min Z: "); + _maxX->setPrefix("Max X: "); + _maxY->setPrefix("Max Y: "); + _maxZ->setPrefix("Max Z: "); + + _minX->setMinimum(-99); + _minY->setMinimum(-99); + _minZ->setMinimum(-99); + _maxX->setMinimum(-99); + _maxY->setMinimum(-99); + _maxZ->setMinimum(-99); + + _minX->setMaximum(99); + _minY->setMaximum(99); + _minZ->setMaximum(99); + _maxX->setMaximum(99); + _maxY->setMaximum(99); + _maxZ->setMaximum(99); + + QWidget::connect(_minX, SIGNAL(valueChanged(double)), this, + SLOT(MinXChanged(double))); + QWidget::connect(_minY, SIGNAL(valueChanged(double)), this, + SLOT(MinYChanged(double))); + QWidget::connect(_minZ, SIGNAL(valueChanged(double)), this, + SLOT(MinZChanged(double))); + QWidget::connect(_maxX, SIGNAL(valueChanged(double)), this, + SLOT(MaxXChanged(double))); + QWidget::connect(_maxY, SIGNAL(valueChanged(double)), this, + SLOT(MaxYChanged(double))); + QWidget::connect(_maxZ, SIGNAL(valueChanged(double)), this, + SLOT(MaxZChanged(double))); + + QGridLayout *controlsLayout = new QGridLayout; + controlsLayout->addWidget(_minX, 0, 0); + controlsLayout->addWidget(_minY, 0, 1); + controlsLayout->addWidget(_minZ, 0, 2); + controlsLayout->addWidget(_maxX, 1, 0); + controlsLayout->addWidget(_maxY, 1, 1); + controlsLayout->addWidget(_maxZ, 1, 2); + QWidget *controls = new QWidget; + controls->setLayout(controlsLayout); + controls->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + std::unordered_map widgetPlaceholders = { + {"{{controls}}", controls}, + {"{{xPos}}", _xPos}, + {"{{yPos}}", _yPos}, + {"{{zPos}}", _zPos}, + }; + QHBoxLayout *line1 = new QHBoxLayout; + placeWidgets(obs_module_text( + "AdvSceneSwitcher.condition.openvr.entry.line1"), + line1, widgetPlaceholders); + QHBoxLayout *line2 = new QHBoxLayout; + placeWidgets(obs_module_text( + "AdvSceneSwitcher.condition.openvr.entry.line2"), + line2, widgetPlaceholders); + QHBoxLayout *line3 = new QHBoxLayout; + placeWidgets(obs_module_text( + "AdvSceneSwitcher.condition.openvr.entry.line3"), + line3, widgetPlaceholders); + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(line1); + mainLayout->addLayout(line2); + mainLayout->addLayout(line3); + mainLayout->addWidget(_errLabel); + setLayout(mainLayout); + + connect(&_timer, &QTimer::timeout, this, + &MacroConditionOpenVREdit::UpdateOpenVRPos); + _timer.start(1000); + + _entryData = entryData; + UpdateEntryData(); + _loading = false; +} + +void MacroConditionOpenVREdit::MinXChanged(double pos) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(GetSwitcher()->m); + _entryData->_minX = pos; +} + +void MacroConditionOpenVREdit::MinYChanged(double pos) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(GetSwitcher()->m); + _entryData->_minY = pos; +} + +void MacroConditionOpenVREdit::MinZChanged(double pos) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(GetSwitcher()->m); + _entryData->_minZ = pos; +} + +void MacroConditionOpenVREdit::MaxXChanged(double pos) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(GetSwitcher()->m); + _entryData->_maxX = pos; +} + +void MacroConditionOpenVREdit::MaxYChanged(double pos) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(GetSwitcher()->m); + _entryData->_maxY = pos; +} + +void MacroConditionOpenVREdit::MaxZChanged(double pos) +{ + if (_loading || !_entryData) { + return; + } + + std::lock_guard lock(GetSwitcher()->m); + _entryData->_maxZ = pos; +} + +void MacroConditionOpenVREdit::UpdateOpenVRPos() +{ + vr::EVRInitError err; + TrackingData data = getOpenVRPos(err); + if (data.valid) { + _xPos->setText(QString::number(data.x)); + _yPos->setText(QString::number(data.y)); + _zPos->setText(QString::number(data.z)); + } else { + _xPos->setText("-"); + _yPos->setText("-"); + _zPos->setText("-"); + _errLabel->setText( + QString(obs_module_text( + "AdvSceneSwitcher.condition.errorStatus")) + + QString(vr::VR_GetVRInitErrorAsEnglishDescription(err))); + } + _errLabel->setVisible(!data.valid); + adjustSize(); +} + +void MacroConditionOpenVREdit::UpdateEntryData() +{ + if (!_entryData) { + return; + } + + _minX->setValue(_entryData->_minX); + _minY->setValue(_entryData->_minY); + _maxX->setValue(_entryData->_maxX); + _maxY->setValue(_entryData->_maxY); +} diff --git a/src/external-macro-modules/openvr/macro-condition-openvr.hpp b/src/external-macro-modules/openvr/macro-condition-openvr.hpp new file mode 100644 index 00000000..7ee3550c --- /dev/null +++ b/src/external-macro-modules/openvr/macro-condition-openvr.hpp @@ -0,0 +1,68 @@ +#pragma once +#include + +#include +#include +#include +#include + +class MacroConditionOpenVR : public MacroCondition { +public: + bool CheckCondition(); + bool Save(obs_data_t *obj); + bool Load(obs_data_t *obj); + std::string GetId() { return id; }; + static std::shared_ptr Create() + { + return std::make_shared(); + } + + double _minX = 0, _minY = 0, _minZ = 0, _maxX = 0, _maxY = 0, _maxZ = 0; + +private: + static bool _registered; + static const std::string id; +}; + +class MacroConditionOpenVREdit : public QWidget { + Q_OBJECT + +public: + MacroConditionOpenVREdit( + QWidget *parent, + std::shared_ptr cond = nullptr); + void UpdateEntryData(); + static QWidget *Create(QWidget *parent, + std::shared_ptr cond) + { + return new MacroConditionOpenVREdit( + parent, + std::dynamic_pointer_cast(cond)); + } + +private slots: + void MinXChanged(double pos); + void MinYChanged(double pos); + void MinZChanged(double pos); + void MaxXChanged(double pos); + void MaxYChanged(double pos); + void MaxZChanged(double pos); + void UpdateOpenVRPos(); + +protected: + QDoubleSpinBox *_minX; + QDoubleSpinBox *_minY; + QDoubleSpinBox *_minZ; + QDoubleSpinBox *_maxX; + QDoubleSpinBox *_maxY; + QDoubleSpinBox *_maxZ; + QLabel *_xPos; + QLabel *_yPos; + QLabel *_zPos; + QLabel *_errLabel; + std::shared_ptr _entryData; + +private: + QTimer _timer; + bool _loading = true; +};