#include "overlaycontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "utils/Matrix.h" // application namespace namespace advsettings { QSettings* OverlayController::_appSettings = nullptr; OverlayController::OverlayController( bool desktopMode, bool noSound, QQmlEngine& qmlEngine ) : QObject(), m_desktopMode( desktopMode ), m_noSound( noSound ), m_actions() { m_runtimePathUrl = QUrl::fromLocalFile( vr::VR_RuntimePath() ); LOG( INFO ) << "VR Runtime Path: " << m_runtimePathUrl.toLocalFile(); QString activationSoundFile = m_runtimePathUrl.toLocalFile().append( "/content/panorama/sounds/activation.wav" ); QFileInfo activationSoundFileInfo( activationSoundFile ); if ( activationSoundFileInfo.exists() && activationSoundFileInfo.isFile() ) { m_activationSoundEffect.setSource( QUrl::fromLocalFile( activationSoundFile ) ); m_activationSoundEffect.setVolume( 1.0 ); } else { LOG( ERROR ) << "Could not find activation sound file " << activationSoundFile; } QString focusChangedSoundFile = m_runtimePathUrl.toLocalFile().append( "/content/panorama/sounds/focus_change.wav" ); QFileInfo focusChangedSoundFileInfo( focusChangedSoundFile ); if ( focusChangedSoundFileInfo.exists() && focusChangedSoundFileInfo.isFile() ) { m_focusChangedSoundEffect.setSource( QUrl::fromLocalFile( focusChangedSoundFile ) ); m_focusChangedSoundEffect.setVolume( 1.0 ); } else { LOG( ERROR ) << "Could not find focus changed sound file " << focusChangedSoundFile; } constexpr auto alarmFileName = "res/sounds/alarm01.wav"; const auto alarm01SoundFile = paths::binaryDirectoryFindFile( alarmFileName ); if ( alarm01SoundFile.has_value() ) { m_alarm01SoundEffect.setSource( QUrl::fromLocalFile( QString::fromStdString( ( *alarm01SoundFile ) ) ) ); m_alarm01SoundEffect.setVolume( 1.0 ); } else { LOG( ERROR ) << "Could not find alarm01 sound file " << alarmFileName; } QSurfaceFormat format; // Qt's QOpenGLPaintDevice is not compatible with OpenGL versions >= 3.0 // NVIDIA does not care, but unfortunately AMD does // Are subtle changes to the semantics of OpenGL functions actually covered // by the compatibility profile, and this is an AMD bug? format.setVersion( 2, 1 ); // format.setProfile( QSurfaceFormat::CompatibilityProfile ); format.setDepthBufferSize( 16 ); format.setStencilBufferSize( 8 ); format.setSamples( 16 ); m_pOpenGLContext.reset( new QOpenGLContext() ); m_pOpenGLContext->setFormat( format ); if ( !m_pOpenGLContext->create() ) { throw std::runtime_error( "Could not create OpenGL context" ); } // create an offscreen surface to attach the context and FBO to m_pOffscreenSurface.reset( new QOffscreenSurface() ); m_pOffscreenSurface->setFormat( m_pOpenGLContext->format() ); m_pOffscreenSurface->create(); m_pOpenGLContext->makeCurrent( m_pOffscreenSurface.get() ); if ( !vr::VROverlay() ) { QMessageBox::critical( nullptr, "OpenVR Advanced Settings Overlay", "Is OpenVR running?" ); throw std::runtime_error( std::string( "No Overlay interface" ) ); } // Init controllers m_steamVRTabController.initStage1(); m_chaperoneTabController.initStage1(); m_moveCenterTabController.initStage1(); m_fixFloorTabController.initStage1(); m_audioTabController.initStage1(); m_statisticsTabController.initStage1(); m_settingsTabController.initStage1(); m_reviveTabController.initStage1( m_settingsTabController.forceRevivePage() ); m_utilitiesTabController.initStage1(); m_videoTabController.initStage1(); // init action handles m_chaperoneTabController.setLeftHapticActionHandle( m_actions.leftHapticActionHandle() ); m_chaperoneTabController.setRightHapticActionHandle( m_actions.rightHapticActionHandle() ); m_chaperoneTabController.setLeftInputHandle( m_actions.leftInputHandle() ); m_chaperoneTabController.setRightInputHandle( m_actions.rightInputHandle() ); // Set qml context qmlEngine.rootContext()->setContextProperty( "applicationVersion", getVersionString() ); qmlEngine.rootContext()->setContextProperty( "vrRuntimePath", getVRRuntimePathUrl() ); // Pretty disgusting trick to allow qmlRegisterSingletonType to continue // working with the lambdas that were already there. The callback function // in qmlRegisterSingletonType won't work with any lambdas that capture the // environment. The alternative to making a static pointer to this was // rewriting all QML to not be singletons, which should probably be done // whenever possible. static OverlayController* const objectAddress = this; constexpr auto qmlSingletonImportName = "ovras.advsettings"; qmlRegisterSingletonType( qmlSingletonImportName, 1, 0, "OverlayController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = objectAddress; QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); // It is unknown if it it intended for the generic in // qmlRegisterSingletonType to be in all the // remaining function calls, or if it's just a copy paste accident that // happens to work. qmlRegisterSingletonType( qmlSingletonImportName, 1, 0, "SteamVRTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_steamVRTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( qmlSingletonImportName, 1, 0, "ChaperoneTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_chaperoneTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( qmlSingletonImportName, 1, 0, "MoveCenterTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_moveCenterTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( qmlSingletonImportName, 1, 0, "FixFloorTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_fixFloorTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( qmlSingletonImportName, 1, 0, "AudioTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_audioTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( qmlSingletonImportName, 1, 0, "StatisticsTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_statisticsTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( qmlSingletonImportName, 1, 0, "SettingsTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_settingsTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( qmlSingletonImportName, 1, 0, "ReviveTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_reviveTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( qmlSingletonImportName, 1, 0, "UtilitiesTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_utilitiesTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( qmlSingletonImportName, 1, 0, "VideoTabController", [](QQmlEngine*, QJSEngine*) { QObject* obj = &(objectAddress->m_videoTabController); QQmlEngine::setObjectOwnership(obj, QQmlEngine::CppOwnership); return obj; }); } OverlayController::~OverlayController() { Shutdown(); } void OverlayController::Shutdown() { if ( m_pPumpEventsTimer ) { disconnect( m_pPumpEventsTimer.get(), SIGNAL( timeout() ), this, SLOT( OnTimeoutPumpEvents() ) ); m_pPumpEventsTimer->stop(); m_pPumpEventsTimer.reset(); } if ( m_pRenderTimer ) { disconnect( m_pRenderControl.get(), SIGNAL( renderRequested() ), this, SLOT( OnRenderRequest() ) ); disconnect( m_pRenderControl.get(), SIGNAL( sceneChanged() ), this, SLOT( OnRenderRequest() ) ); disconnect( m_pRenderTimer.get(), SIGNAL( timeout() ), this, SLOT( renderOverlay() ) ); m_pRenderTimer->stop(); m_pRenderTimer.reset(); } m_pWindow.reset(); m_pRenderControl.reset(); m_pFbo.reset(); m_pOpenGLContext.reset(); m_pOffscreenSurface.reset(); } void OverlayController::SetWidget( QQuickItem* quickItem, const std::string& name, const std::string& key ) { if ( !m_desktopMode ) { vr::VROverlayError overlayError = vr::VROverlay()->CreateDashboardOverlay( key.c_str(), name.c_str(), &m_ulOverlayHandle, &m_ulOverlayThumbnailHandle ); if ( overlayError != vr::VROverlayError_None ) { if ( overlayError == vr::VROverlayError_KeyInUse ) { QMessageBox::critical( nullptr, "OpenVR Advanced Settings Overlay", "Another instance is already running." ); } throw std::runtime_error( std::string( "Failed to create Overlay: " + std::string( vr::VROverlay()->GetOverlayErrorNameFromEnum( overlayError ) ) ) ); } vr::VROverlay()->SetOverlayWidthInMeters( m_ulOverlayHandle, 2.5f ); vr::VROverlay()->SetOverlayInputMethod( m_ulOverlayHandle, vr::VROverlayInputMethod_Mouse ); vr::VROverlay()->SetOverlayFlag( m_ulOverlayHandle, vr::VROverlayFlags_SendVRSmoothScrollEvents, true ); constexpr auto thumbiconFilename = "res/img/icons/thumbicon.png"; const auto thumbIconPath = paths::binaryDirectoryFindFile( thumbiconFilename ); if ( thumbIconPath.has_value() ) { vr::VROverlay()->SetOverlayFromFile( m_ulOverlayThumbnailHandle, thumbIconPath->c_str() ); } else { LOG( ERROR ) << "Could not find thumbnail icon \"" << thumbiconFilename << "\""; } // Too many render calls in too short time overwhelm Qt and an assertion // gets thrown. Therefore we use an timer to delay render calls m_pRenderTimer.reset( new QTimer() ); m_pRenderTimer->setSingleShot( true ); m_pRenderTimer->setInterval( 5 ); connect( m_pRenderTimer.get(), SIGNAL( timeout() ), this, SLOT( renderOverlay() ) ); QOpenGLFramebufferObjectFormat fboFormat; fboFormat.setAttachment( QOpenGLFramebufferObject::CombinedDepthStencil ); fboFormat.setTextureTarget( GL_TEXTURE_2D ); m_pFbo.reset( new QOpenGLFramebufferObject( static_cast( quickItem->width() ), static_cast( quickItem->height() ), fboFormat ) ); m_pRenderControl.reset( new QQuickRenderControl() ); m_pWindow.reset( new QQuickWindow( m_pRenderControl.get() ) ); m_pWindow->setRenderTarget( m_pFbo.get() ); quickItem->setParentItem( m_pWindow->contentItem() ); m_pWindow->setGeometry( 0, 0, static_cast( quickItem->width() ), static_cast( quickItem->height() ) ); m_pRenderControl->initialize( m_pOpenGLContext.get() ); vr::HmdVector2_t vecWindowSize = { static_cast( quickItem->width() ), static_cast( quickItem->height() ) }; vr::VROverlay()->SetOverlayMouseScale( m_ulOverlayHandle, &vecWindowSize ); connect( m_pRenderControl.get(), SIGNAL( renderRequested() ), this, SLOT( OnRenderRequest() ) ); connect( m_pRenderControl.get(), SIGNAL( sceneChanged() ), this, SLOT( OnRenderRequest() ) ); } m_pPumpEventsTimer.reset( new QTimer() ); connect( m_pPumpEventsTimer.get(), SIGNAL( timeout() ), this, SLOT( OnTimeoutPumpEvents() ) ); // Every 1ms we check if the current frame has advanced (for vysnc) m_pPumpEventsTimer->setInterval( 1 ); m_pPumpEventsTimer->start(); m_steamVRTabController.initStage2( this, m_pWindow.get() ); m_chaperoneTabController.initStage2( this, m_pWindow.get() ); m_moveCenterTabController.initStage2( this, m_pWindow.get() ); m_fixFloorTabController.initStage2( this, m_pWindow.get() ); m_audioTabController.initStage2( this, m_pWindow.get() ); m_statisticsTabController.initStage2( this, m_pWindow.get() ); m_settingsTabController.initStage2( this, m_pWindow.get() ); m_reviveTabController.initStage2( this, m_pWindow.get() ); m_utilitiesTabController.initStage2( this, m_pWindow.get() ); m_videoTabController.initStage2(this, m_pWindow.get()); } void OverlayController::OnRenderRequest() { if ( m_pRenderTimer && !m_pRenderTimer->isActive() ) { m_pRenderTimer->start(); } } void OverlayController::renderOverlay() { if ( !m_desktopMode ) { // skip rendering if the overlay isn't visible if ( !vr::VROverlay() || ( !vr::VROverlay()->IsOverlayVisible( m_ulOverlayHandle ) && !vr::VROverlay()->IsOverlayVisible( m_ulOverlayThumbnailHandle ) ) ) return; m_pRenderControl->polishItems(); m_pRenderControl->sync(); m_pRenderControl->render(); GLuint unTexture = m_pFbo->texture(); if ( unTexture != 0 ) { #if defined _WIN64 || defined _LP64 // To avoid any compiler warning because of cast to a larger pointer // type (warning C4312 on VC) vr::Texture_t texture = { reinterpret_cast( static_cast( unTexture ) ), vr::TextureType_OpenGL, vr::ColorSpace_Auto }; #else vr::Texture_t texture = { reinterpret_cast( unTexture ), vr::TextureType_OpenGL, vr::ColorSpace_Auto }; #endif vr::VROverlay()->SetOverlayTexture( m_ulOverlayHandle, &texture ); } m_pOpenGLContext->functions()->glFlush(); // We need to flush otherwise // the texture may be empty.*/ } } bool OverlayController::pollNextEvent( vr::VROverlayHandle_t ulOverlayHandle, vr::VREvent_t* pEvent ) { if ( isDesktopMode() ) { return vr::VRSystem()->PollNextEvent( pEvent, sizeof( vr::VREvent_t ) ); } else { return vr::VROverlay()->PollNextOverlayEvent( ulOverlayHandle, pEvent, sizeof( vr::VREvent_t ) ); } } QPoint OverlayController::getMousePositionForEvent( vr::VREvent_Mouse_t mouse ) { float y = mouse.y; #ifdef __linux__ float h = static_cast( m_pWindow->height() ); y = h - y; #endif return QPoint( static_cast( mouse.x ), static_cast( y ) ); } void OverlayController::processMediaKeyBindings() { if ( m_actions.nextSong() ) { m_utilitiesTabController.sendMediaNextSong(); } if ( m_actions.previousSong() ) { m_utilitiesTabController.sendMediaPreviousSong(); } if ( m_actions.pausePlaySong() ) { m_utilitiesTabController.sendMediaPausePlay(); } if ( m_actions.stopSong() ) { m_utilitiesTabController.sendMediaStopSong(); } } void OverlayController::processMotionBindings() { // Execution order for moveCenterTabController actions is important. Don't // reorder these. Override actions must always come after normal because // active priority is set based on which action is "newest" // normal actions: m_moveCenterTabController.leftHandSpaceDrag( m_actions.leftHandSpaceDrag() ); m_moveCenterTabController.rightHandSpaceDrag( m_actions.rightHandSpaceDrag() ); m_moveCenterTabController.leftHandSpaceTurn( m_actions.leftHandSpaceTurn() ); m_moveCenterTabController.rightHandSpaceTurn( m_actions.rightHandSpaceTurn() ); m_moveCenterTabController.gravityToggleAction( m_actions.gravityToggle() ); m_moveCenterTabController.gravityReverseAction( m_actions.gravityReverse() ); m_moveCenterTabController.heightToggleAction( m_actions.heightToggle() ); m_moveCenterTabController.resetOffsets( m_actions.resetOffsets() ); m_moveCenterTabController.snapTurnLeft( m_actions.snapTurnLeft() ); m_moveCenterTabController.snapTurnRight( m_actions.snapTurnRight() ); m_moveCenterTabController.xAxisLockToggle( m_actions.xAxisLockToggle() ); m_moveCenterTabController.yAxisLockToggle( m_actions.yAxisLockToggle() ); m_moveCenterTabController.zAxisLockToggle( m_actions.zAxisLockToggle() ); // override actions: m_moveCenterTabController.optionalOverrideLeftHandSpaceDrag( m_actions.optionalOverrideLeftHandSpaceDrag() ); m_moveCenterTabController.optionalOverrideRightHandSpaceDrag( m_actions.optionalOverrideRightHandSpaceDrag() ); m_moveCenterTabController.optionalOverrideLeftHandSpaceTurn( m_actions.optionalOverrideLeftHandSpaceTurn() ); m_moveCenterTabController.optionalOverrideRightHandSpaceTurn( m_actions.optionalOverrideRightHandSpaceTurn() ); m_moveCenterTabController.swapSpaceDragToLeftHandOverride( m_actions.swapSpaceDragToLeftHandOverride() ); m_moveCenterTabController.swapSpaceDragToRightHandOverride( m_actions.swapSpaceDragToRightHandOverride() ); } void OverlayController::processPushToTalkBindings() { const auto pushToTalkCannotChange = !m_audioTabController.pttChangeValid(); const auto pushToTalkEnabled = m_audioTabController.pttEnabled(); if ( pushToTalkCannotChange || !pushToTalkEnabled ) { return; } const auto pushToTalkButtonActivated = m_actions.pushToTalk(); const auto pushToTalkCurrentlyActive = m_audioTabController.pttActive(); if ( pushToTalkButtonActivated && !pushToTalkCurrentlyActive ) { m_audioTabController.startPtt(); } else if ( !pushToTalkButtonActivated && pushToTalkCurrentlyActive ) { m_audioTabController.stopPtt(); } } /*! Checks if an action has been activated and dispatches the related action if it has been. */ void OverlayController::processInputBindings() { processMediaKeyBindings(); processMotionBindings(); processPushToTalkBindings(); } // vsync implementation: // (this function triggers every 1ms) void OverlayController::OnTimeoutPumpEvents() { // get the current frame number from the VRSystem frame counter vr::VRSystem()->GetTimeSinceLastVsync( nullptr, &m_currentFrame ); // Check if we are in the next frame yet if ( m_currentFrame > m_lastFrame ) { // If the frame has advanced since last check, it's time for our main // event loop. (this function should trigger about every 11ms assuming // 90fps compositor) mainEventLoop(); // wait for the next frame after executing our main event loop once. m_lastFrame = m_currentFrame; m_vsyncTooLateCounter = 0; } else if ( m_vsyncTooLateCounter >= k_nonVsyncTickRate ) { mainEventLoop(); // m_lastFrame = m_currentFrame + 1 skips the next vsync frame in case // it was just about to trigger, to prevent double updates faster than // 11ms. m_lastFrame = m_currentFrame + 1; m_vsyncTooLateCounter = 0; } m_vsyncTooLateCounter++; } void OverlayController::mainEventLoop() { if ( !vr::VRSystem() ) return; m_actions.UpdateStates(); processInputBindings(); vr::VREvent_t vrEvent; bool chaperoneDataAlreadyUpdated = false; while ( pollNextEvent( m_ulOverlayHandle, &vrEvent ) ) { switch ( vrEvent.eventType ) { case vr::VREvent_MouseMove: { QPoint ptNewMouse = getMousePositionForEvent( vrEvent.data.mouse ); if ( ptNewMouse != m_ptLastMouse ) { QMouseEvent mouseEvent( QEvent::MouseMove, ptNewMouse, m_pWindow->mapToGlobal( ptNewMouse ), Qt::NoButton, m_lastMouseButtons, nullptr ); m_ptLastMouse = ptNewMouse; QCoreApplication::sendEvent( m_pWindow.get(), &mouseEvent ); OnRenderRequest(); } } break; case vr::VREvent_MouseButtonDown: { QPoint ptNewMouse = getMousePositionForEvent( vrEvent.data.mouse ); Qt::MouseButton button = vrEvent.data.mouse.button == vr::VRMouseButton_Right ? Qt::RightButton : Qt::LeftButton; m_lastMouseButtons |= button; QMouseEvent mouseEvent( QEvent::MouseButtonPress, ptNewMouse, m_pWindow->mapToGlobal( ptNewMouse ), button, m_lastMouseButtons, nullptr ); QCoreApplication::sendEvent( m_pWindow.get(), &mouseEvent ); } break; case vr::VREvent_MouseButtonUp: { QPoint ptNewMouse = getMousePositionForEvent( vrEvent.data.mouse ); Qt::MouseButton button = vrEvent.data.mouse.button == vr::VRMouseButton_Right ? Qt::RightButton : Qt::LeftButton; m_lastMouseButtons &= ~button; QMouseEvent mouseEvent( QEvent::MouseButtonRelease, ptNewMouse, m_pWindow->mapToGlobal( ptNewMouse ), button, m_lastMouseButtons, nullptr ); QCoreApplication::sendEvent( m_pWindow.get(), &mouseEvent ); } break; case vr::VREvent_ScrollSmooth: { // Wheel speed is defined as 1/8 of a degree QWheelEvent wheelEvent( m_ptLastMouse, m_pWindow->mapToGlobal( m_ptLastMouse ), QPoint(), QPoint( static_cast( vrEvent.data.scroll.xdelta * ( 360.0f * 8.0f ) ), static_cast( vrEvent.data.scroll.ydelta * ( 360.0f * 8.0f ) ) ), 0, Qt::Vertical, m_lastMouseButtons, nullptr ); QCoreApplication::sendEvent( m_pWindow.get(), &wheelEvent ); } break; case vr::VREvent_OverlayShown: { m_pWindow->update(); } break; case vr::VREvent_Quit: { LOG( INFO ) << "Received quit request."; vr::VRSystem()->AcknowledgeQuit_Exiting(); // Let us buy some // time just in case m_moveCenterTabController.reset(); // Un-mute mic before Exiting VR, as it is set at system level Not // Vr level. m_audioTabController.setMicMuted( false, false ); m_chaperoneTabController.shutdown(); Shutdown(); QApplication::exit(); LOG( INFO ) << "All systems exited."; exit( EXIT_SUCCESS ); // Does not fallthrough } case vr::VREvent_DashboardActivated: { LOG( DEBUG ) << "Dashboard activated"; m_dashboardVisible = true; } break; case vr::VREvent_DashboardDeactivated: { LOG( DEBUG ) << "Dashboard deactivated"; m_dashboardVisible = false; } break; case vr::VREvent_KeyboardDone: { char keyboardBuffer[1024]; vr::VROverlay()->GetKeyboardText( keyboardBuffer, 1024 ); emit keyBoardInputSignal( QString( keyboardBuffer ), static_cast( vrEvent.data.keyboard.uUserValue ) ); } break; // Multiple ChaperoneUniverseHasChanged are often emitted at the // same time (some with a little bit of delay) There is no sure way // to recognize redundant events, we can only exclude redundant // events during the same call of OnTimeoutPumpEvents() INFO Removed // logging on play space mover for possible crashing issues. case vr::VREvent_ChaperoneUniverseHasChanged: case vr::VREvent_ChaperoneDataHasChanged: { if ( !chaperoneDataAlreadyUpdated ) { // LOG(INFO) << "Re-loading chaperone data ..."; m_chaperoneUtils.loadChaperoneData(); // LOG(INFO) << "Found " << m_chaperoneUtils.quadsCount() << // " chaperone quads."; if // (m_chaperoneUtils.isChaperoneWellFormed()) { LOG(INFO) << // "Chaperone data seems to be well-formed."; //} else { // LOG(INFO) << "Chaperone data is NOT well-formed."; //} chaperoneDataAlreadyUpdated = true; } } break; } } vr::TrackedDevicePose_t devicePoses[vr::k_unMaxTrackedDeviceCount]; vr::VRSystem()->GetDeviceToAbsoluteTrackingPose( vr::TrackingUniverseStanding, 0.0f, devicePoses, vr::k_unMaxTrackedDeviceCount ); // HMD/Controller Velocities auto leftId = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole( vr::TrackedControllerRole_LeftHand ); float leftSpeed = 0.0f; if ( leftId != vr::k_unTrackedDeviceIndexInvalid && devicePoses[leftId].bPoseIsValid && devicePoses[leftId].eTrackingResult == vr::TrackingResult_Running_OK ) { auto& vel = devicePoses[leftId].vVelocity.v; leftSpeed = std::sqrt( vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2] ); } auto rightId = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole( vr::TrackedControllerRole_RightHand ); auto rightSpeed = 0.0f; if ( rightId != vr::k_unTrackedDeviceIndexInvalid && devicePoses[rightId].bPoseIsValid && devicePoses[rightId].eTrackingResult == vr::TrackingResult_Running_OK ) { auto& vel = devicePoses[rightId].vVelocity.v; rightSpeed = std::sqrt( vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2] ); } auto hmdSpeed = 0.0f; if ( devicePoses[vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid && devicePoses[vr::k_unTrackedDeviceIndex_Hmd].eTrackingResult == vr::TrackingResult_Running_OK ) { auto& vel = devicePoses[vr::k_unTrackedDeviceIndex_Hmd].vVelocity.v; hmdSpeed = std::sqrt( vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2] ); } m_moveCenterTabController.eventLoopTick( vr::VRCompositor()->GetTrackingSpace(), devicePoses ); m_utilitiesTabController.eventLoopTick(); m_fixFloorTabController.eventLoopTick( devicePoses ); m_statisticsTabController.eventLoopTick( devicePoses, leftSpeed, rightSpeed ); m_steamVRTabController.eventLoopTick(); m_chaperoneTabController.eventLoopTick( devicePoses, leftSpeed, rightSpeed, hmdSpeed ); m_settingsTabController.eventLoopTick(); m_reviveTabController.eventLoopTick(); m_audioTabController.eventLoopTick(); //TODO do I need loop for vide? if ( m_ulOverlayThumbnailHandle != vr::k_ulOverlayHandleInvalid ) { while ( vr::VROverlay()->PollNextOverlayEvent( m_ulOverlayThumbnailHandle, &vrEvent, sizeof( vrEvent ) ) ) { switch ( vrEvent.eventType ) { case vr::VREvent_OverlayShown: { m_pWindow->update(); } break; } } } } void OverlayController::AddOffsetToUniverseCenter( vr::ETrackingUniverseOrigin universe, unsigned axisId, float offset, bool adjustBounds, bool commit ) { float offsetArray[3] = { 0, 0, 0 }; offsetArray[axisId] = offset; AddOffsetToUniverseCenter( universe, offsetArray, adjustBounds, commit ); } void OverlayController::AddOffsetToUniverseCenter( vr::ETrackingUniverseOrigin universe, float offset[3], bool adjustBounds, bool commit ) { if ( offset[0] != 0.0f || offset[1] != 0.0f || offset[2] != 0.0f ) { if ( commit ) { vr::VRChaperoneSetup()->HideWorkingSetPreview(); vr::VRChaperoneSetup()->RevertWorkingCopy(); } vr::HmdMatrix34_t curPos; if ( universe == vr::TrackingUniverseStanding ) { vr::VRChaperoneSetup()->GetWorkingStandingZeroPoseToRawTrackingPose( &curPos ); } else { vr::VRChaperoneSetup()->GetWorkingSeatedZeroPoseToRawTrackingPose( &curPos ); } for ( int i = 0; i < 3; i++ ) { curPos.m[0][3] += curPos.m[0][i] * offset[i]; curPos.m[1][3] += curPos.m[1][i] * offset[i]; curPos.m[2][3] += curPos.m[2][i] * offset[i]; } if ( universe == vr::TrackingUniverseStanding ) { vr::VRChaperoneSetup()->SetWorkingStandingZeroPoseToRawTrackingPose( &curPos ); } else { vr::VRChaperoneSetup()->SetWorkingSeatedZeroPoseToRawTrackingPose( &curPos ); } if ( adjustBounds && universe == vr::TrackingUniverseStanding ) { float collisionOffset[] = { -offset[0], -offset[1], -offset[2] }; AddOffsetToCollisionBounds( collisionOffset, false ); } if ( commit ) { vr::VRChaperoneSetup()->CommitWorkingCopy( vr::EChaperoneConfigFile_Live ); } } } void OverlayController::RotateUniverseCenter( vr::ETrackingUniverseOrigin universe, float yAngle, bool adjustBounds, bool commit ) { if ( yAngle != 0.0f ) { if ( commit ) { vr::VRChaperoneSetup()->HideWorkingSetPreview(); vr::VRChaperoneSetup()->RevertWorkingCopy(); } vr::HmdMatrix34_t curPos; if ( universe == vr::TrackingUniverseStanding ) { vr::VRChaperoneSetup()->GetWorkingStandingZeroPoseToRawTrackingPose( &curPos ); } else { vr::VRChaperoneSetup()->GetWorkingSeatedZeroPoseToRawTrackingPose( &curPos ); } vr::HmdMatrix34_t rotMat; vr::HmdMatrix34_t newPos; utils::initRotationMatrix( rotMat, 1, yAngle ); utils::matMul33( newPos, rotMat, curPos ); newPos.m[0][3] = curPos.m[0][3]; newPos.m[1][3] = curPos.m[1][3]; newPos.m[2][3] = curPos.m[2][3]; if ( universe == vr::TrackingUniverseStanding ) { vr::VRChaperoneSetup()->SetWorkingStandingZeroPoseToRawTrackingPose( &newPos ); } else { vr::VRChaperoneSetup()->SetWorkingSeatedZeroPoseToRawTrackingPose( &newPos ); } if ( adjustBounds && universe == vr::TrackingUniverseStanding ) { RotateCollisionBounds( -yAngle, false ); } if ( commit ) { vr::VRChaperoneSetup()->CommitWorkingCopy( vr::EChaperoneConfigFile_Live ); } } } void OverlayController::AddOffsetToCollisionBounds( unsigned axisId, float offset, bool commit ) { float offsetArray[3] = { 0, 0, 0 }; offsetArray[axisId] = offset; AddOffsetToCollisionBounds( offsetArray, commit ); } void OverlayController::AddOffsetToCollisionBounds( float offset[3], bool commit ) { // Apparently Valve sanity-checks the y-coordinates of the collision bounds // (and only the y-coordinates) I can move the bounds on the xz-plane, I can // make the "ceiling" of the chaperone cage lower/higher, but when I dare to // set one single lower corner to something non-zero, every corner gets its // y-coordinates reset to the defaults. if ( commit ) { vr::VRChaperoneSetup()->HideWorkingSetPreview(); vr::VRChaperoneSetup()->RevertWorkingCopy(); } unsigned collisionBoundsCount = 0; vr::VRChaperoneSetup()->GetWorkingCollisionBoundsInfo( nullptr, &collisionBoundsCount ); if ( collisionBoundsCount > 0 ) { vr::HmdQuad_t* collisionBounds = new vr::HmdQuad_t[collisionBoundsCount]; vr::VRChaperoneSetup()->GetWorkingCollisionBoundsInfo( collisionBounds, &collisionBoundsCount ); for ( unsigned b = 0; b < collisionBoundsCount; b++ ) { for ( unsigned c = 0; c < 4; c++ ) { collisionBounds[b].vCorners[c].v[0] += offset[0]; // keep the lower corners on the ground so it doesn't reset all // y cooridinates. this causes the caperone to "grow" up instead // of not moving up at all. note that Valve still forces a // minimum height so we can't go into the ground if ( collisionBounds[b].vCorners[c].v[1] != 0 ) { collisionBounds[b].vCorners[c].v[1] += offset[1]; } collisionBounds[b].vCorners[c].v[2] += offset[2]; } } vr::VRChaperoneSetup()->SetWorkingCollisionBoundsInfo( collisionBounds, collisionBoundsCount ); delete[] collisionBounds; } if ( commit && collisionBoundsCount > 0 ) { vr::VRChaperoneSetup()->CommitWorkingCopy( vr::EChaperoneConfigFile_Live ); } } void OverlayController::RotateCollisionBounds( float angle, bool commit ) { if ( commit ) { vr::VRChaperoneSetup()->HideWorkingSetPreview(); vr::VRChaperoneSetup()->RevertWorkingCopy(); } unsigned collisionBoundsCount = 0; vr::VRChaperoneSetup()->GetWorkingCollisionBoundsInfo( nullptr, &collisionBoundsCount ); if ( collisionBoundsCount > 0 ) { vr::HmdQuad_t* collisionBounds = new vr::HmdQuad_t[collisionBoundsCount]; vr::VRChaperoneSetup()->GetWorkingCollisionBoundsInfo( collisionBounds, &collisionBoundsCount ); vr::HmdMatrix34_t rotMat; utils::initRotationMatrix( rotMat, 1, angle ); for ( unsigned b = 0; b < collisionBoundsCount; b++ ) { for ( unsigned c = 0; c < 4; c++ ) { auto& corner = collisionBounds[b].vCorners[c]; vr::HmdVector3_t newVal; utils::matMul33( newVal, rotMat, corner ); corner = newVal; } } vr::VRChaperoneSetup()->SetWorkingCollisionBoundsInfo( collisionBounds, collisionBoundsCount ); delete[] collisionBounds; } if ( commit && collisionBoundsCount > 0 ) { vr::VRChaperoneSetup()->CommitWorkingCopy( vr::EChaperoneConfigFile_Live ); } } QString OverlayController::getVersionString() { return QString( application_strings::applicationVersionString ); } QUrl OverlayController::getVRRuntimePathUrl() { return m_runtimePathUrl; } bool OverlayController::soundDisabled() { return m_noSound; } const vr::VROverlayHandle_t& OverlayController::overlayHandle() { return m_ulOverlayHandle; } const vr::VROverlayHandle_t& OverlayController::overlayThumbnailHandle() { return m_ulOverlayThumbnailHandle; } void OverlayController::showKeyboard( QString existingText, unsigned long userValue ) { vr::VROverlay()->ShowKeyboardForOverlay( m_ulOverlayHandle, vr::k_EGamepadTextInputModeNormal, vr::k_EGamepadTextInputLineModeSingleLine, "Advanced Settings Overlay", 1024, existingText.toStdString().c_str(), false, userValue ); } void OverlayController::playActivationSound() { if ( !m_noSound ) { m_activationSoundEffect.play(); } } void OverlayController::playFocusChangedSound() { if ( !m_noSound ) { m_focusChangedSoundEffect.play(); } } void OverlayController::playAlarm01Sound( bool loop ) { if ( !m_noSound && !m_alarm01SoundEffect.isPlaying() ) { if ( loop ) { m_alarm01SoundEffect.setLoopCount( QSoundEffect::Infinite ); } else { m_alarm01SoundEffect.setLoopCount( 1 ); } m_alarm01SoundEffect.play(); } } void OverlayController::setAlarm01SoundVolume( float vol ) { m_alarm01SoundEffect.setVolume( static_cast( vol ) ); } void OverlayController::cancelAlarm01Sound() { m_alarm01SoundEffect.stop(); } } // namespace advsettings