#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 { constexpr const char* OverlayController::applicationVersionString; QSettings* OverlayController::_appSettings = nullptr; OverlayController::OverlayController( bool desktopMode, bool noSound, QQmlEngine& qmlEngine ) : QObject(), m_desktopMode( desktopMode ), m_noSound( noSound ), m_openVrInit(), m_actions() { // Despite arguably being OpenVR init code, the call is still here because // the TabController uses this directly. Offering it through OpenVR_Init // might be an option, but it might scope creep OpenVR_Init which currently // doesn't contain any member variables. 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; } QString alarm01SoundFile = QStandardPaths::locate( QStandardPaths::AppDataLocation, QStringLiteral( "res/sounds/alarm01.wav" ) ); QFileInfo alarm01SoundFileInfo( alarm01SoundFile ); if ( alarm01SoundFileInfo.exists() && alarm01SoundFileInfo.isFile() ) { m_alarm01SoundEffect.setSource( QUrl::fromLocalFile( alarm01SoundFile ) ); m_alarm01SoundEffect.setVolume( 1.0 ); } else { LOG( ERROR ) << "Could not find alarm01 sound file " << alarm01SoundFile; } 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_accessibilityTabController.initStage1(); // 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; qmlRegisterSingletonType( "matzman666.advsettings", 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( "matzman666.advsettings", 1, 0, "SteamVRTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_steamVRTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( "matzman666.advsettings", 1, 0, "ChaperoneTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_chaperoneTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( "matzman666.advsettings", 1, 0, "MoveCenterTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_moveCenterTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( "matzman666.advsettings", 1, 0, "FixFloorTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_fixFloorTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( "matzman666.advsettings", 1, 0, "AudioTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_audioTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( "matzman666.advsettings", 1, 0, "StatisticsTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_statisticsTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( "matzman666.advsettings", 1, 0, "SettingsTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_settingsTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( "matzman666.advsettings", 1, 0, "ReviveTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_reviveTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( "matzman666.advsettings", 1, 0, "UtilitiesTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_utilitiesTabController ); QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); qmlRegisterSingletonType( "matzman666.advsettings", 1, 0, "AccessibilityTabController", []( QQmlEngine*, QJSEngine* ) { QObject* obj = &( objectAddress->m_accessibilityTabController ); 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_SendVRScrollEvents, true ); QString thumbIconPath = QStandardPaths::locate( QStandardPaths::AppDataLocation, QStringLiteral( "res/thumbicon.png" ) ); if ( QFile::exists( thumbIconPath ) ) { vr::VROverlay()->SetOverlayFromFile( m_ulOverlayThumbnailHandle, thumbIconPath.toStdString().c_str() ); } else { LOG( ERROR ) << "Could not find thumbnail icon \"" << thumbIconPath << "\""; } // 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( quickItem->width(), 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, quickItem->width(), quickItem->height() ); m_pRenderControl->initialize( m_pOpenGLContext.get() ); vr::HmdVector2_t vecWindowSize = { ( float ) quickItem->width(), ( float ) 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() ) ); m_pPumpEventsTimer->setInterval( 20 ); 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_accessibilityTabController.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 = { ( void* ) ( ( uint64_t ) unTexture ), vr::TextureType_OpenGL, vr::ColorSpace_Auto }; #else vr::Texture_t texture = { ( void* ) 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 = ( float ) m_pWindow->height(); y = h - y; #endif return QPoint( mouse.x, y ); } void OverlayController::OnTimeoutPumpEvents() { if ( !vr::VRSystem() ) return; m_actions.Loop(); if ( m_actions.nextSong() ) { LOG( INFO ) << "NEXT SONG SET!"; } 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, 0 ); 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, 0 ); 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, 0 ); QCoreApplication::sendEvent( m_pWindow.get(), &mouseEvent ); } break; case vr::VREvent_Scroll: { // Wheel speed is defined as 1/8 of a degree QWheelEvent wheelEvent( m_ptLastMouse, m_pWindow->mapToGlobal( m_ptLastMouse ), QPoint(), QPoint( vrEvent.data.scroll.xdelta * 360.0f * 8.0f, vrEvent.data.scroll.ydelta * 360.0f * 8.0f ), 0, Qt::Vertical, m_lastMouseButtons, 0 ); 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(); m_chaperoneTabController.shutdown(); Shutdown(); QApplication::exit(); return; } break; 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 ), 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_fixFloorTabController.eventLoopTick( devicePoses ); m_statisticsTabController.eventLoopTick( devicePoses, leftSpeed, rightSpeed ); m_moveCenterTabController.eventLoopTick( vr::VRCompositor()->GetTrackingSpace(), devicePoses ); m_steamVRTabController.eventLoopTick(); m_chaperoneTabController.eventLoopTick( devicePoses, leftSpeed, rightSpeed, hmdSpeed ); m_settingsTabController.eventLoopTick(); m_reviveTabController.eventLoopTick(); m_audioTabController.eventLoopTick(); m_utilitiesTabController.eventLoopTick(); m_accessibilityTabController.eventLoopTick( vr::VRCompositor()->GetTrackingSpace() ); 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()->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()->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()->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()->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( 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( vol ); } void OverlayController::cancelAlarm01Sound() { m_alarm01SoundEffect.stop(); } } // namespace advsettings