diff --git a/build_scripts/qt/resources.pri b/build_scripts/qt/resources.pri index 0357034..259cd38 100644 --- a/build_scripts/qt/resources.pri +++ b/build_scripts/qt/resources.pri @@ -23,9 +23,7 @@ DISTFILES += \ src/res/qml/motion_page/* \ src/res/qml/motion_page/gravity/* \ src/res/qml/motion_page/height_toggle/* \ - src/res/qml/motion_page/snap_turn/* \ src/res/qml/motion_page/space_drag/* \ - src/res/qml/motion_page/space_turn/* \ src/res/qml/RootPage.qml \ src/res/qml/SettingsPage.qml \ src/res/qml/StatisticsPage.qml \ @@ -49,6 +47,10 @@ DISTFILES += \ src/res/qml/video_page/profiles/* \ src/res/qml/video_page/dialogboxes/* \ src/res/qml/video_page/overlaycolorbright/* \ + src/res/qml/rotation_page/* \ + src/res/qml/rotation_page/redirected/* \ + src/res/qml/rotation_page/snap_turn/* \ + src/res/qml/rotation_page/space_turn/* \ src/res/qml/common/* win32:RC_ICONS += src/res/img/icons/advicon256px.ico diff --git a/build_scripts/qt/sources.pri b/build_scripts/qt/sources.pri index 81c8689..dbff1e8 100644 --- a/build_scripts/qt/sources.pri +++ b/build_scripts/qt/sources.pri @@ -9,6 +9,7 @@ SOURCES += src/main.cpp\ src/tabcontrollers/SteamVRTabController.cpp \ src/tabcontrollers/UtilitiesTabController.cpp \ src/tabcontrollers/VideoTabController.cpp \ + src/tabcontrollers/RotationTabController.cpp\ src/utils/ChaperoneUtils.cpp \ src/openvr/openvr_init.cpp \ src/openvr/ivrinput.cpp \ @@ -21,6 +22,7 @@ SOURCES += src/main.cpp\ src/settings/settings_object.cpp \ + HEADERS += src/overlaycontroller.h \ src/tabcontrollers/AudioTabController.h \ src/tabcontrollers/ChaperoneTabController.h \ @@ -32,6 +34,7 @@ HEADERS += src/overlaycontroller.h \ src/tabcontrollers/UtilitiesTabController.h \ src/tabcontrollers/VideoTabController.h \ src/tabcontrollers/audiomanager/AudioManager.h \ + src/tabcontrollers/RotationTabController.h\ src/keyboard_input/keyboard_input.h \ src/media_keys/media_keys.h \ src/utils/Matrix.h \ @@ -56,6 +59,7 @@ HEADERS += src/overlaycontroller.h \ src/settings/settings_object.h \ src/settings/internal/settings_object_data.h + win32 { SOURCES += src/tabcontrollers/audiomanager/AudioManagerWindows.cpp \ src/keyboard_input/input_sender_win.cpp \ diff --git a/src/openvr/ivrinput.cpp b/src/openvr/ivrinput.cpp index f8bf076..ea9fb16 100644 --- a/src/openvr/ivrinput.cpp +++ b/src/openvr/ivrinput.cpp @@ -130,6 +130,7 @@ SteamIVRInput::SteamIVRInput() m_snapTurnRight( action_keys::snapTurnRight ), m_smoothTurnLeft( action_keys::smoothTurnLeft ), m_smoothTurnRight( action_keys::smoothTurnRight ), + m_autoTurnToggle( action_keys::autoTurnToggle ), m_xAxisLockToggle( action_keys::xAxisLockToggle ), m_yAxisLockToggle( action_keys::yAxisLockToggle ), m_zAxisLockToggle( action_keys::zAxisLockToggle ), @@ -288,6 +289,10 @@ bool SteamIVRInput::smoothTurnRight() { return isDigitalActionActivatedConstant( m_smoothTurnRight ); } +bool SteamIVRInput::autoTurnToggle() +{ + return isDigitalActionActivatedOnce( m_autoTurnToggle ); +} bool SteamIVRInput::xAxisLockToggle() { diff --git a/src/openvr/ivrinput.h b/src/openvr/ivrinput.h index 30a2ce9..f289170 100644 --- a/src/openvr/ivrinput.h +++ b/src/openvr/ivrinput.h @@ -45,6 +45,7 @@ namespace action_keys constexpr auto snapTurnRight = "/actions/motion/in/SnapTurnRight"; constexpr auto smoothTurnLeft = "/actions/motion/in/SmoothTurnLeft"; constexpr auto smoothTurnRight = "/actions/motion/in/SmoothTurnRight"; + constexpr auto autoTurnToggle = "/actions/motion/in/AutoTurnToggle"; constexpr auto xAxisLockToggle = "/actions/misc/in/XAxisLockToggle"; constexpr auto yAxisLockToggle = "/actions/misc/in/YAxisLockToggle"; @@ -142,6 +143,7 @@ public: bool snapTurnRight(); bool smoothTurnLeft(); bool smoothTurnRight(); + bool autoTurnToggle(); bool xAxisLockToggle(); bool yAxisLockToggle(); bool zAxisLockToggle(); @@ -212,6 +214,7 @@ private: DigitalAction m_snapTurnRight; DigitalAction m_smoothTurnLeft; DigitalAction m_smoothTurnRight; + DigitalAction m_autoTurnToggle; DigitalAction m_xAxisLockToggle; DigitalAction m_yAxisLockToggle; DigitalAction m_zAxisLockToggle; diff --git a/src/overlaycontroller.cpp b/src/overlaycontroller.cpp index 036af8a..8bbd0a7 100644 --- a/src/overlaycontroller.cpp +++ b/src/overlaycontroller.cpp @@ -152,6 +152,7 @@ OverlayController::OverlayController( bool desktopMode, m_settingsTabController.initStage1(); m_utilitiesTabController.initStage1(); m_videoTabController.initStage1(); + m_rotationTabController.initStage1(); // init action handles @@ -281,6 +282,16 @@ OverlayController::OverlayController( bool desktopMode, QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); return obj; } ); + qmlRegisterSingletonType( + qmlSingletonImportName, + 1, + 0, + "RotationTabController", + []( QQmlEngine*, QJSEngine* ) { + QObject* obj = &( objectAddress->m_rotationTabController ); + QQmlEngine::setObjectOwnership( obj, QQmlEngine::CppOwnership ); + return obj; + } ); // Grab local version number QStringList verNumericalString @@ -455,6 +466,7 @@ void OverlayController::SetWidget( QQuickItem* quickItem, m_settingsTabController.initStage2( this ); m_utilitiesTabController.initStage2( this ); m_moveCenterTabController.initStage2( this ); + m_rotationTabController.initStage2( this ); } void OverlayController::OnRenderRequest() @@ -668,6 +680,14 @@ void OverlayController::processKeyboardBindings() sendStringAsInput( commands ); } } +void OverlayController::processRotationBindings() +{ + if ( m_actions.autoTurnToggle() ) + { + m_rotationTabController.setAutoTurnEnabled( + !( m_rotationTabController.autoTurnEnabled() ) ); + } +} /*! Checks if an action has been activated and dispatches the related action if it has been. @@ -683,6 +703,8 @@ void OverlayController::processInputBindings() processChaperoneBindings(); processKeyboardBindings(); + + processRotationBindings(); } bool OverlayController::crashRecoveryDisabled() const @@ -1121,6 +1143,7 @@ void OverlayController::mainEventLoop() devicePoses, leftSpeed, rightSpeed ); m_chaperoneTabController.eventLoopTick( devicePoses ); m_audioTabController.eventLoopTick(); + m_rotationTabController.eventLoopTick( devicePoses ); if ( vr::VROverlay()->IsDashboardVisible() ) { diff --git a/src/overlaycontroller.h b/src/overlaycontroller.h index 6408f52..5205230 100644 --- a/src/overlaycontroller.h +++ b/src/overlaycontroller.h @@ -39,6 +39,7 @@ #include "tabcontrollers/SettingsTabController.h" #include "tabcontrollers/UtilitiesTabController.h" #include "tabcontrollers/VideoTabController.h" +#include "tabcontrollers/RotationTabController.h" #include "openvr/ivrinput.h" @@ -166,6 +167,7 @@ public: // I know it's an ugly hack to make them public to enable external SettingsTabController m_settingsTabController; UtilitiesTabController m_utilitiesTabController; VideoTabController m_videoTabController; + RotationTabController m_rotationTabController; private: QPoint getMousePositionForEvent( vr::VREvent_Mouse_t mouse ); @@ -175,6 +177,7 @@ private: void processPushToTalkBindings(); void processChaperoneBindings(); void processKeyboardBindings(); + void processRotationBindings(); public: OverlayController( bool desktopMode, bool noSound, QQmlEngine& qmlEngine ); diff --git a/src/package_files/action_manifest.json b/src/package_files/action_manifest.json index e3053cc..1cfd79f 100644 --- a/src/package_files/action_manifest.json +++ b/src/package_files/action_manifest.json @@ -154,6 +154,11 @@ "requirement": "optional", "type": "boolean" }, + { + "name": "/actions/motion/in/AutoTurnToggle", + "requirement": "optional", + "type": "boolean" + }, { "name": "/actions/misc/in/XAxisLockToggle", @@ -257,6 +262,7 @@ "/actions/motion/in/SnapTurnRight" : "Snap-Turn Right", "/actions/motion/in/SmoothTurnLeft" : "Smooth-Turn Left", "/actions/motion/in/SmoothTurnRight" : "Smooth-Turn Right", + "/actions/motion/in/AutoTurnToggle" : "Auto-Turn Toggle", "/actions/misc/in/XAxisLockToggle" : "X-Axis Lock Toggle", "/actions/misc/in/YAxisLockToggle" : "Y-Axis Lock Toggle", diff --git a/src/res/img/main_menu_icons/rotation_tab_icon.svg b/src/res/img/main_menu_icons/rotation_tab_icon.svg new file mode 100644 index 0000000..0735790 --- /dev/null +++ b/src/res/img/main_menu_icons/rotation_tab_icon.svg @@ -0,0 +1,13 @@ + + + + + + + diff --git a/src/res/qml/RootPage.qml b/src/res/qml/RootPage.qml index b6612ae..681f952 100644 --- a/src/res/qml/RootPage.qml +++ b/src/res/qml/RootPage.qml @@ -73,6 +73,17 @@ MyStackViewPage { mainView.push(motionPage) } } + MyPushButton { + id: rotationButton + iconPath: "qrc:/main_menu_icons/rotation_tab_icon" + activationSoundEnabled: false + text: " Rotation" + Layout.fillWidth: true + onClicked: { + MyResources.playFocusChangedSound() + mainView.push(rotationPage) + } + } MyPushButton { id: fixFloorButton diff --git a/src/res/qml/common/mainwidget.qml b/src/res/qml/common/mainwidget.qml index b0d647f..a0d0520 100644 --- a/src/res/qml/common/mainwidget.qml +++ b/src/res/qml/common/mainwidget.qml @@ -8,6 +8,7 @@ import "../motion_page" import "../video_page" import "../chaperone_page" import "../steamvr_page" +import "../rotation_page" Rectangle { id: root @@ -44,6 +45,11 @@ Rectangle { visible: false } + property RotationPage rotationPage: RotationPage { + stackView: mainView + visible: false + } + property FixFloorPage fixFloorPage: FixFloorPage { stackView: mainView visible: false diff --git a/src/res/qml/motion_page/MotionPage.qml b/src/res/qml/motion_page/MotionPage.qml index e639420..26587e3 100644 --- a/src/res/qml/motion_page/MotionPage.qml +++ b/src/res/qml/motion_page/MotionPage.qml @@ -4,10 +4,8 @@ import QtQuick.Layouts 1.3 import ovras.advsettings 1.0 import "../common" import "space_drag" -import "space_turn" import "height_toggle" import "gravity" -import "snap_turn" MyStackViewPage { headerText: "Motion Settings" @@ -17,13 +15,10 @@ MyStackViewPage { SpaceDragGroupBox { id: spaceDragGroupBox } - SpaceTurnGroupBox { id: spaceTurnGroupBox } - HeightToggleGroupBox { id: heightToggleGroupBox } GravityGroupBox { id: gravityGroupBox } - SnapTurnGroupBox { id: snapTurnGroupBox } Item { Layout.fillHeight: true diff --git a/src/res/qml/rotation_page/RotationPage.qml b/src/res/qml/rotation_page/RotationPage.qml new file mode 100644 index 0000000..9299309 --- /dev/null +++ b/src/res/qml/rotation_page/RotationPage.qml @@ -0,0 +1,38 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.3 +import ovras.advsettings 1.0 +import "../common" +import "redirected" +import "space_turn" +import "snap_turn" + + +MyStackViewPage { + width: 1200 + headerText: "Rotation Settings" + content: ColumnLayout { + spacing: 10 + AutoTurnGroupBox{ + id: autoTurnGroupBox + } + + RedirectedGroupBox{ + id: redirectedGroupBox + } + + SpaceTurnGroupBox{ + id:spaceTurnGroupBox + } + + SnapTurnGroupBox{ + id: snapTurnGroupBox + } + + + Item { + Layout.fillHeight: true + } + + } +} diff --git a/src/res/qml/rotation_page/redirected/AutoTurnGroupBox.qml b/src/res/qml/rotation_page/redirected/AutoTurnGroupBox.qml new file mode 100644 index 0000000..bd4bee0 --- /dev/null +++ b/src/res/qml/rotation_page/redirected/AutoTurnGroupBox.qml @@ -0,0 +1,336 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.3 +import ovras.advsettings 1.0 +import "../../common" + +GroupBox { + id: autoTurnGroupBox + Layout.fillWidth: true + + label: MyText { + leftPadding: 10 + text: "Auto-Turn Settings" + bottomPadding: -10 + } + background: Rectangle { + color: "transparent" + border.color: "#ffffff" + radius: 8 + } + + ColumnLayout { + anchors.fill: parent + + Rectangle { + color: "#ffffff" + height: 1 + Layout.fillWidth: true + Layout.bottomMargin: 5 + } + + RowLayout { + Layout.fillWidth: true + + MyToggleButton { + id: autoTurn + text: "Toggle On/Off" + Layout.preferredWidth: 300 + onCheckedChanged: { + RotationTabController.setAutoTurnEnabled(this.checked, true); } + } + + MyText { + text: "Activation Dist:" + horizontalAlignment: Text.AlignRight + Layout.rightMargin: 10 + Layout.preferredWidth: 215 + } + + MySlider { + id: activationSlider + from: 0 + to: 1.0 + stepSize: 0.05 + value: .4 + Layout.fillWidth: true + onPositionChanged: { + var val = this.value + activationValueText.text = val.toFixed(2) + } + onValueChanged: { + + RotationTabController.setAutoTurnActivationDistance(value, true) + } + } + + MyTextField { + id: activationValueText + text: "0.4" + keyBoardUID: 1001 + Layout.preferredWidth: 100 + Layout.leftMargin: 10 + horizontalAlignment: Text.AlignHCenter + function onInputEvent(input) { + var val = parseFloat(input) + if (!isNaN(val)) { + if (val < 0) { + val = 0 + } else if (val > 22) { + val = 22 + } + activationSlider.value = v; + } + text = RotationTabController.autoTurnActivationDistance; + } + } + } + RowLayout { + Layout.fillWidth: true + + MyToggleButton { + id: cornerAngle + text: "Use Corner Angle" + Layout.preferredWidth: 300 + onCheckedChanged: { + RotationTabController.setAutoTurnUseCornerAngle(this.checked, true); + } + } + + MyText { + text: "Deactivation Dist:" + horizontalAlignment: Text.AlignRight + Layout.rightMargin: 10 + Layout.preferredWidth: 215 + } + + MySlider { + id: deactivationSlider + from: 0 + to: 1.0 + stepSize: 0.05 + value: .15 + Layout.fillWidth: true + onPositionChanged: { + var val = this.value + deactivationValueText.text = val.toFixed(2) + } + onValueChanged: { + + RotationTabController.setAutoTurnDeactivationDistance(value, true) + } + } + + MyTextField { + id: deactivationValueText + text: "0.15" + keyBoardUID: 1002 + Layout.preferredWidth: 100 + Layout.leftMargin: 10 + horizontalAlignment: Text.AlignHCenter + function onInputEvent(input) { + var val = parseFloat(input) + if (!isNaN(val)) { + if (val < 0) { + val = 0 + } else if (val > 22) { + val = 22 + } + deactivationSlider.value = v; + } + text = RotationTabController.autoTurnDeactivationDistance; + } + } + } + + RowLayout { + MyToggleButton { + id: autoTurnModeToggle + text: "Use Smooth Turn" + Layout.preferredWidth: 300 + onCheckedChanged: { + if(this.checked){ + + RotationTabController.setAutoTurnMode(1, true); + }else{ + RotationTabController.setAutoTurnMode(0, true); + } + + } + } + + MyText { + text: "Turn Speed (deg/sec):" + horizontalAlignment: Text.AlignRight + Layout.rightMargin: 10 + } + + MySlider { + id: speedSlider + from: 0 + to: 360 + stepSize: 1 + value: 90 + Layout.fillWidth: true + onPositionChanged: { + var val = this.value + speedValueText.text = val.toFixed() + } + onValueChanged: { + + RotationTabController.setAutoTurnSpeed(parseInt(value*100), true) + } + } + + MyTextField { + id: speedValueText + text: "90" + keyBoardUID: 1003 + Layout.preferredWidth: 100 + Layout.leftMargin: 10 + horizontalAlignment: Text.AlignHCenter + function onInputEvent(input) { + var val = parseFloat(input) + if (!isNaN(val)) { + if (val < 1) { + val = 1 + //caps user set Speed to 1000 deg/Second + //used as overflow prevention + //arbitrary + } else if (val > 1000) { + val = 1000 + } + speedSlider.value = v; + } + //converts the centidegrees to degrees + text = (((RotationTabController.autoTurnSpeed)/100).toFixed()); + } + } + + + } + RowLayout{ + MyText{ + text: "Detangle Angle: " + horizontalAlignment: Text.AlignLeft + Layout.preferredWidth: 250 + Layout.rightMargin: 5 + + } + MyText{ + text:"Min Rotations(deg):" + Layout.rightMargin: 5 + + } + + MyTextField { + id: detangleAngleStartText + text: "360" + keyBoardUID: 1005 + Layout.preferredWidth: 100 + Layout.leftMargin: 5 + horizontalAlignment: Text.AlignHCenter + function onInputEvent(input) { + var val = parseFloat(input) + if (!isNaN(val)) { + if (val < 0) { + val = 0 + } else if (val > 3600) { + val = 3600 + } + RotationTabController.setMinCordTangle((val*(Math.PI/180)), true); + } + } + } + Item { + Layout.preferredWidth: 15 + } + + MyText{ + text: "Max Wall Angle(deg): " + horizontalAlignment: Text.AlignRight + Layout.rightMargin: 10 + Layout.fillWidth: true + + } + MyTextField { + id: detangleAngleAssistText + text: "10" + keyBoardUID: 1006 + Layout.preferredWidth: 100 + Layout.leftMargin: 10 + horizontalAlignment: Text.AlignHCenter + function onInputEvent(input) { + var val = parseFloat(input) + if (!isNaN(val)) { + if (val < 0) { + val = 0 + } else if (val > 3600) { + val = 3600 + } + RotationTabController.setCordDetangleAngle((val*(Math.PI/180)), true); + } + } + } + } + + + + + } + + Component.onCompleted: { + activationSlider.value = RotationTabController.autoTurnActivationDistance + autoTurn.checked = RotationTabController.autoTurnEnabled + deactivationSlider.value = RotationTabController.autoTurnDeactivationDistance + cornerAngle.checked = RotationTabController.autoTurnUseCornerAngle + speedSlider.value = (RotationTabController.autoTurnSpeed/100).toFixed() + if(RotationTabController.autoTurnMode === 1){ + + autoTurnModeToggle.checked = true; + + }else{ + + autoTurnModeToggle.checked = false; + } + detangleAngleStartText.text = parseInt(RotationTabController.minCordTangle*(180/Math.PI)) + detangleAngleAssistText.text = parseInt(RotationTabController.cordDetangleAngle*(180/Math.PI)) + } + + Connections { + target: RotationTabController + + onAutoTurnEnabledChanged: { + autoTurn.checked = RotationTabController.autoTurnEnabled + } + onAutoTurnActivationDistanceChanged: { + activationSlider.value = RotationTabController.autoTurnActivationDistance + } + onAutoTurnDeactivationDistanceChanged:{ + deactivationSlider.value = RotationTabController.autoTurnDeactivationDistance + } + onAutoTurnUseCornerAngleChanged:{ + cornerAngle.checked = RotationTabController.autoTurnUseCornerAngle + } + onAutoTurnSpeedChanged:{ + var val = RotationTabController.autoTurnSpeed + speedSlider.value = ((val/100).toFixed()) + } + onAutoTurnModeChanged:{ + if(RotationTabController.autoTurnMode === 1){ + + autoTurnModeToggle.checked = true; + + }else{ + + autoTurnModeToggle.checked = false; + } + } + onMinCordTangleChanged:{ + detangleAngleStartText.text = parseInt(RotationTabController.minCordTangle*(180/Math.PI)) + } + onCordDetangleAngleChanged:{ + detangleAngleAssistText.text = parseInt(RotationTabController.cordDetangleAngle*(180/Math.PI)) + } + } +} diff --git a/src/res/qml/rotation_page/redirected/RedirectedGroupBox.qml b/src/res/qml/rotation_page/redirected/RedirectedGroupBox.qml new file mode 100644 index 0000000..c9ec39f --- /dev/null +++ b/src/res/qml/rotation_page/redirected/RedirectedGroupBox.qml @@ -0,0 +1,125 @@ +import QtQuick 2.7 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.3 +import ovras.advsettings 1.0 +import "../../common" + +GroupBox { + id: redirectedGroupBox + Layout.fillWidth: true + + label: MyText { + leftPadding: 10 + text: "Redirected Walking" + bottomPadding: -10 + } + background: Rectangle { + color: "transparent" + border.color: "#ffffff" + radius: 8 + } + + ColumnLayout { + anchors.fill: parent + + Rectangle { + color: "#ffffff" + height: 1 + Layout.fillWidth: true + Layout.bottomMargin: 5 + } + + RowLayout{ + MyToggleButton { + id: redirectedModeToggle + text: "On" + Layout.preferredWidth: 225 + onCheckedChanged: { + RotationTabController.setVestibularMotionEnabled(this.checked, true); + + } + } + Item{ + + Layout.fillWidth: true + } + + MyPushButton { + id: imperceptableButton + Layout.preferredWidth: 180 + text:"Imperceptable" + onClicked: { + RotationTabController.setVestibularMotionRadius(22.0) + } + } + MyPushButton { + id: lightButton + Layout.preferredWidth: 125 + text:"Light" + onClicked: { + RotationTabController.setVestibularMotionRadius(11.0) + } + } + MyPushButton { + id: strongButton + Layout.preferredWidth: 125 + text:"Strong" + onClicked: { + RotationTabController.setVestibularMotionRadius(5.0) + } + } + MyPushButton { + id: extremeButton + Layout.preferredWidth: 125 + text:"Extreme" + onClicked: { + RotationTabController.setVestibularMotionRadius(1.0) + } + } + + MyText{ + text: "Radius: " + horizontalAlignment: Text.AlignRight + Layout.rightMargin: 4 + } + + + MyTextField { + id: redirectedWalkingRadiusText + text: "11.0" + keyBoardUID: 1004 + Layout.preferredWidth: 100 + Layout.leftMargin: 10 + horizontalAlignment: Text.AlignHCenter + function onInputEvent(input) { + var val = parseFloat(input) + if (!isNaN(val)) { + if (val < 0.5) { + val = 0.5 + } else if (val > 50) { + val = 50 + } + RotationTabController.setVestibularMotionRadius(val, true); + } + } + } + } + } + + Component.onCompleted: { + redirectedWalkingRadiusText.text = ((RotationTabController.vestibularMotionRadius).toFixed(2)) + redirectedModeToggle.checked = RotationTabController.vestibularMotionEnabled + } + + Connections { + target: RotationTabController + + onVestibularMotionEnabledChanged:{ + redirectedModeToggle.checked = RotationTabController.vestibularMotionEnabled + } + onVestibularMotionRadiusChanged:{ + redirectedWalkingRadiusText.text = ((RotationTabController.vestibularMotionRadius).toFixed(2)) + } + } + +} diff --git a/src/res/qml/motion_page/snap_turn/SnapTurnGroupBox.qml b/src/res/qml/rotation_page/snap_turn/SnapTurnGroupBox.qml similarity index 100% rename from src/res/qml/motion_page/snap_turn/SnapTurnGroupBox.qml rename to src/res/qml/rotation_page/snap_turn/SnapTurnGroupBox.qml diff --git a/src/res/qml/motion_page/space_turn/SpaceTurnGroupBox.qml b/src/res/qml/rotation_page/space_turn/SpaceTurnGroupBox.qml similarity index 100% rename from src/res/qml/motion_page/space_turn/SpaceTurnGroupBox.qml rename to src/res/qml/rotation_page/space_turn/SpaceTurnGroupBox.qml diff --git a/src/res/resources.qrc b/src/res/resources.qrc index 7e68951..7c6192d 100644 --- a/src/res/resources.qrc +++ b/src/res/resources.qrc @@ -29,5 +29,6 @@ img/main_menu_icons/steamvr_tab_icon.svg img/main_menu_icons/utilities_tab_icon.svg img/main_menu_icons/video_tab_icon.svg + img/main_menu_icons/rotation_tab_icon.svg \ No newline at end of file diff --git a/src/settings/internal/settings_controller.h b/src/settings/internal/settings_controller.h index 3afdf80..0469f78 100644 --- a/src/settings/internal/settings_controller.h +++ b/src/settings/internal/settings_controller.h @@ -7,6 +7,7 @@ #include "setting_value.h" #include "../../utils/setup.h" #include "specific_setting_value.h" +#include "../../tabcontrollers/MoveCenterTabController.h" namespace settings { @@ -357,6 +358,18 @@ private: SettingCategory::Chaperone, QtInfo{ "disableChaperone" }, false }, + BoolSettingValue{ BoolSetting::ROTATION_autoturnEnabled, + SettingCategory::Rotation, + QtInfo{ "autoturnEnabled" }, + false }, + BoolSettingValue{ BoolSetting::ROTATION_autoturnUseCornerAngle, + SettingCategory::Rotation, + QtInfo{ "autoturnUseCornerAngle" }, + true }, + BoolSettingValue{ BoolSetting::ROTATION_autoturnVestibularMotionEnabled, + SettingCategory::Rotation, + QtInfo{ "autoturnVestibularMotionEnabled" }, + false }, }; constexpr static auto doubleSettingSize @@ -416,6 +429,28 @@ private: SettingCategory::Chaperone, QtInfo{ "fadeDistanceRemembered" }, 0.5 }, + DoubleSettingValue{ DoubleSetting::ROTATION_activationDistance, + SettingCategory::Rotation, + QtInfo{ "activationDistance" }, + 0.4 }, + DoubleSettingValue{ DoubleSetting::ROTATION_deactivateDistance, + SettingCategory::Rotation, + QtInfo{ "deactivateDistance" }, + 0.15 }, + DoubleSettingValue{ DoubleSetting::ROTATION_cordDetanglingAngle, + SettingCategory::Rotation, + QtInfo{ "cordDetanglingAngle" }, + 1000 * advsettings::k_centidegreesToRadians }, + DoubleSettingValue{ DoubleSetting::ROTATION_autoturnMinCordTangle, + SettingCategory::Rotation, + QtInfo{ "autoturnMinCordTangle" }, + 2 * M_PI }, + DoubleSettingValue{ + DoubleSetting::ROTATION_autoturnVestibularMotionRadius, + SettingCategory::Rotation, + QtInfo{ "autoturnVestibularMotionRadius" }, + 11.0 }, + }; constexpr static auto stringSettingsSize @@ -477,6 +512,14 @@ private: SettingCategory::Utility, QtInfo{ "alarmMinute" }, 0 }, + IntSettingValue{ IntSetting::ROTATION_autoturnLinearTurnSpeed, + SettingCategory::Rotation, + QtInfo{ "autoturnLinearTurnSpeed" }, + 9000 }, + IntSettingValue{ IntSetting::ROTATION_autoturnMode, + SettingCategory::Rotation, + QtInfo{ "autoturnMode" }, + 1 }, }; }; diff --git a/src/settings/internal/settings_internal.h b/src/settings/internal/settings_internal.h index d0428ea..5f13b07 100644 --- a/src/settings/internal/settings_internal.h +++ b/src/settings/internal/settings_internal.h @@ -16,6 +16,7 @@ enum class SettingCategory Video, Chaperone, ChaperoneProfiles, + Rotation, }; struct QtInfo @@ -53,6 +54,8 @@ struct QtInfo return "chaperoneSettings"; case SettingCategory::ChaperoneProfiles: return "chaperoneProfiles"; + case SettingCategory::Rotation: + return "rotationSettings"; } return "no-value"; } diff --git a/src/settings/settings.h b/src/settings/settings.h index 5d55dd9..11dba11 100644 --- a/src/settings/settings.h +++ b/src/settings/settings.h @@ -49,8 +49,12 @@ enum class BoolSetting CHAPERONE_chaperoneAlarmSoundAdjustVolume, CHAPERONE_chaperoneShowDashboardEnabled, CHAPERONE_disableChaperone, + + ROTATION_autoturnEnabled, + ROTATION_autoturnUseCornerAngle, + ROTATION_autoturnVestibularMotionEnabled, // LAST_ENUMERATOR must always be set to the last value - LAST_ENUMERATOR = CHAPERONE_disableChaperone, + LAST_ENUMERATOR = ROTATION_autoturnVestibularMotionEnabled, }; enum class DoubleSetting @@ -70,8 +74,14 @@ enum class DoubleSetting CHAPERONE_alarmSoundDistance, CHAPERONE_showDashboardDistance, CHAPERONE_fadeDistanceRemembered, + + ROTATION_activationDistance, + ROTATION_deactivateDistance, + ROTATION_cordDetanglingAngle, + ROTATION_autoturnMinCordTangle, + ROTATION_autoturnVestibularMotionRadius, // LAST_ENUMERATOR must always be set to the last value - LAST_ENUMERATOR = CHAPERONE_fadeDistanceRemembered, + LAST_ENUMERATOR = ROTATION_autoturnVestibularMotionRadius, }; enum class StringSetting @@ -96,8 +106,11 @@ enum class IntSetting UTILITY_alarmHour, UTILITY_alarmMinute, + + ROTATION_autoturnLinearTurnSpeed, // LAST_ENUMERATOR must always be set to the last value - LAST_ENUMERATOR = UTILITY_alarmMinute, + ROTATION_autoturnMode, + LAST_ENUMERATOR = ROTATION_autoturnMode, }; std::string initializeAndGetSettingsPath(); diff --git a/src/tabcontrollers/ChaperoneTabController.cpp b/src/tabcontrollers/ChaperoneTabController.cpp index bf3eea8..32ea3f0 100644 --- a/src/tabcontrollers/ChaperoneTabController.cpp +++ b/src/tabcontrollers/ChaperoneTabController.cpp @@ -2,6 +2,8 @@ #include #include "../overlaycontroller.h" #include "../settings/settings.h" +#include "../utils/Matrix.h" +#include "../quaternion/quaternion.h" #include // application namespace @@ -278,9 +280,9 @@ void ChaperoneTabController::eventLoopTick( { poseHmd.mDeviceToAbsoluteTracking.m[0][3], poseHmd.mDeviceToAbsoluteTracking.m[1][3], poseHmd.mDeviceToAbsoluteTracking.m[2][3] } ); - if ( !std::isnan( distanceHmd ) ) + if ( !std::isnan( distanceHmd.distance ) ) { - minDistance = distanceHmd; + minDistance = distanceHmd.distance; } } auto leftIndex = vr::VRSystem()->GetTrackedDeviceIndexForControllerRole( @@ -296,11 +298,11 @@ void ChaperoneTabController::eventLoopTick( { poseLeft.mDeviceToAbsoluteTracking.m[0][3], poseLeft.mDeviceToAbsoluteTracking.m[1][3], poseLeft.mDeviceToAbsoluteTracking.m[2][3] } ); - if ( !std::isnan( distanceLeft ) + if ( !std::isnan( distanceLeft.distance ) && ( std::isnan( minDistance ) - || distanceLeft < minDistance ) ) + || distanceLeft.distance < minDistance ) ) { - minDistance = distanceLeft; + minDistance = distanceLeft.distance; } } } @@ -318,11 +320,11 @@ void ChaperoneTabController::eventLoopTick( { poseRight.mDeviceToAbsoluteTracking.m[0][3], poseRight.mDeviceToAbsoluteTracking.m[1][3], poseRight.mDeviceToAbsoluteTracking.m[2][3] } ); - if ( !std::isnan( distanceRight ) + if ( !std::isnan( distanceRight.distance ) && ( std::isnan( minDistance ) - || distanceRight < minDistance ) ) + || distanceRight.distance < minDistance ) ) { - minDistance = distanceRight; + minDistance = distanceRight.distance; } } } diff --git a/src/tabcontrollers/ChaperoneTabController.h b/src/tabcontrollers/ChaperoneTabController.h index 6af00c2..003ee03 100644 --- a/src/tabcontrollers/ChaperoneTabController.h +++ b/src/tabcontrollers/ChaperoneTabController.h @@ -8,7 +8,9 @@ #include #include #include "../utils/FrameRateUtils.h" +#include "../utils/ChaperoneUtils.h" #include "../settings/settings_object.h" +#include "MoveCenterTabController.h" class QQuickWindow; // application namespace diff --git a/src/tabcontrollers/RotationTabController.cpp b/src/tabcontrollers/RotationTabController.cpp new file mode 100644 index 0000000..2af92c7 --- /dev/null +++ b/src/tabcontrollers/RotationTabController.cpp @@ -0,0 +1,553 @@ +#include "RotationTabController.h" +#include +#include "../overlaycontroller.h" +#include "../settings/settings.h" +#include "../utils/Matrix.h" +#include "../quaternion/quaternion.h" +#include + +// application namespace +namespace advsettings +{ +void RotationTabController::initStage1() +{ // TODO adjust update rate + // m_chaperoneSettingsUpdateCounter + // = utils::adjustUpdateRate( k_chaperoneSettingsUpdateCounter ); +} + +void RotationTabController::initStage2( OverlayController* var_parent ) +{ + this->parent = var_parent; +} + +void RotationTabController::eventLoopTick( + vr::TrackedDevicePose_t* devicePoses ) +{ + if ( devicePoses ) + { + m_isHMDActive = false; + std::lock_guard lock( + parent->chaperoneUtils().mutex() ); + auto& poseHmd = devicePoses[vr::k_unTrackedDeviceIndex_Hmd]; + + // m_isHMDActive is true when prox sensor OR HMD is moving (~10 seconds + // to update from OVR) + // THIS IS A WORK-AROUND Until proper binding support/calls are made + // availble for prox sensor + if ( vr::VRSystem()->GetTrackedDeviceActivityLevel( + vr::k_unTrackedDeviceIndex_Hmd ) + == vr::k_EDeviceActivityLevel_UserInteraction ) + { + m_isHMDActive = true; + } + // TODO: this doesn't need to run if both are off + if ( poseHmd.bPoseIsValid && poseHmd.bDeviceIsConnected + && poseHmd.eTrackingResult == vr::TrackingResult_Running_OK ) + { + auto chaperoneDistances + = parent->chaperoneUtils().getDistancesToChaperone( + { poseHmd.mDeviceToAbsoluteTracking.m[0][3], + poseHmd.mDeviceToAbsoluteTracking.m[1][3], + poseHmd.mDeviceToAbsoluteTracking.m[2][3] } ); + + // Autoturn mode + if ( RotationTabController::autoTurnEnabled() ) + { + doAutoTurn( poseHmd, chaperoneDistances ); + } + // Vestibular motion. Dependent on autoTurn so the playspace + // doesn't move when you use the keybind + if ( RotationTabController::vestibularMotionEnabled() ) + { + doVestibularMotion( poseHmd, chaperoneDistances ); + } + + m_autoTurnChaperoneDistancesLast = std::move( chaperoneDistances ); + } + } +} + +void RotationTabController::doVestibularMotion( + const vr::TrackedDevicePose_t& poseHmd, + const std::vector& chaperoneDistances ) +{ + if ( m_isHMDActive && poseHmd.bPoseIsValid && poseHmd.bDeviceIsConnected + && poseHmd.eTrackingResult == vr::TrackingResult_Running_OK + && !chaperoneDistances.empty() ) + { + if ( chaperoneDistances.size() + != m_autoTurnChaperoneDistancesLast.size() ) + { + m_autoTurnChaperoneDistancesLast = chaperoneDistances; + m_autoTurnLastHmdUpdate = poseHmd.mDeviceToAbsoluteTracking; + } + // Find the nearest wall we're moving TOWARDS and rotate + // away from it Rotate dist/(2*pi*r) where r is + // m_autoTurnVestibularMotionRadius, as if we had walked + // however many inches along a circle and the world was + // turning to compensate + + do + { + // find nearest wall we're moving towards + auto nearestWall = chaperoneDistances.end(); + auto itrLast = m_autoTurnChaperoneDistancesLast.begin(); + for ( auto itr = chaperoneDistances.begin(); + itr != chaperoneDistances.end(); + itr++ ) + { + itrLast++; + if ( itr->distance > itrLast->distance ) + { + continue; + } + + if ( nearestWall == chaperoneDistances.end() + || itr->distance < nearestWall->distance ) + { + nearestWall = itr; + } + } + if ( nearestWall == chaperoneDistances.end() ) + { + break; + } + + // Convert pose matrix to quaternion + auto hmdQuaternion = quaternion::fromHmdMatrix34( + poseHmd.mDeviceToAbsoluteTracking ); + + // Get HMD raw yaw + double hmdYaw = quaternion::getYaw( hmdQuaternion ); + + // Get angle between HMD position and nearest point on + // wall + double hmdPositionToWallYaw = static_cast( + std::atan2( poseHmd.mDeviceToAbsoluteTracking.m[0][3] + - nearestWall->nearestPoint.v[0], + poseHmd.mDeviceToAbsoluteTracking.m[2][3] + - nearestWall->nearestPoint.v[2] ) ); + + // Get angle between HMD and wall + double hmdToWallYaw + = reduceAngle<>( hmdYaw - hmdPositionToWallYaw, -M_PI, M_PI ); + bool turnLeft = hmdToWallYaw > 0.0; + + // Get the distance between previous and current + // position + double distanceChange = std::sqrt( + std::pow( poseHmd.mDeviceToAbsoluteTracking.m[0][3] + - m_autoTurnLastHmdUpdate.m[0][3], + 2.0 ) + + std::pow( poseHmd.mDeviceToAbsoluteTracking.m[0][3] + - m_autoTurnLastHmdUpdate.m[0][3], + 2.0 ) ); + + double rotationAmount + = ( distanceChange + / ( 2.0 * M_PI + * RotationTabController::vestibularMotionRadius() ) ) + * ( turnLeft ? 1 : -1 ); + + double newRotationAngleDeg + = std::fmod( parent->m_moveCenterTabController.rotation() + + ( rotationAmount * k_radiansToCentidegrees ) + + m_autoTurnRoundingError, + 360000.0 ); + int newRotationAngleInt = static_cast( newRotationAngleDeg ); + m_autoTurnRoundingError = newRotationAngleDeg - newRotationAngleInt; + + parent->m_moveCenterTabController.setRotation( + newRotationAngleInt ); + + } while ( false ); + m_autoTurnLastHmdUpdate = poseHmd.mDeviceToAbsoluteTracking; + } +} + +void RotationTabController::doAutoTurn( + const vr::TrackedDevicePose_t& poseHmd, + const std::vector& chaperoneDistances ) +{ + if ( m_isHMDActive && poseHmd.bPoseIsValid && poseHmd.bDeviceIsConnected + && poseHmd.eTrackingResult == vr::TrackingResult_Running_OK + && !chaperoneDistances.empty() ) + { + auto currentTime = std::chrono::steady_clock::now(); + + if ( m_autoTurnWallActive.size() != chaperoneDistances.size() ) + { + // Chaperone changed + m_autoTurnWallActive.clear(); + // Initialize all to 'true' so we don't rotate on startup if + // the user is outside of the play area. + m_autoTurnWallActive.resize( chaperoneDistances.size(), true ); + m_autoTurnLastUpdate = currentTime; + } + + // Apply smooth rotation per-frame: get the time since last frame, + // multiply by speed + if ( RotationTabController::autoTurnModeType() + == AutoTurnModes::LINEAR_SMOOTH_TURN + && m_autoTurnLinearSmoothTurnRemaining != 0 ) + { + // TODO: implement angular acceleration max? + auto deltaMillis + = std::chrono::duration_cast( + currentTime - m_autoTurnLastUpdate ) + .count(); + auto miniDeltaAngle = static_cast( + std::abs( + ( deltaMillis * RotationTabController::autoTurnSpeed() ) + / 1000 ) + * ( m_autoTurnLinearSmoothTurnRemaining < 0 ? -1 : 1 ) ); + if ( std::abs( m_autoTurnLinearSmoothTurnRemaining ) + < std::abs( miniDeltaAngle ) ) + { + miniDeltaAngle = m_autoTurnLinearSmoothTurnRemaining; + } + int newRotationAngleDeg = reduceAngle<>( + static_cast( parent->m_moveCenterTabController.rotation() + + miniDeltaAngle ), + 0, + 36000 ); + + parent->m_moveCenterTabController.setRotation( + newRotationAngleDeg ); + m_autoTurnLinearSmoothTurnRemaining -= miniDeltaAngle; + } + + for ( size_t i = 0; i < chaperoneDistances.size(); i++ ) + { + const auto& chaperoneQuad = chaperoneDistances[i]; + if ( chaperoneQuad.distance + <= RotationTabController::autoTurnActivationDistance() + && !m_autoTurnWallActive[i] ) + { + // Convert pose matrix to quaternion + auto hmdQuaternion = quaternion::fromHmdMatrix34( + poseHmd.mDeviceToAbsoluteTracking ); + + // Get HMD raw yaw + double hmdYaw = quaternion::getYaw( hmdQuaternion ); + + // Get angle between HMD position and nearest point on + // wall + double hmdPositionToWallYaw = static_cast( + std::atan2( poseHmd.mDeviceToAbsoluteTracking.m[0][3] + - chaperoneQuad.nearestPoint.v[0], + poseHmd.mDeviceToAbsoluteTracking.m[2][3] + - chaperoneQuad.nearestPoint.v[2] ) ); + + // Get angle between HMD and wall + double hmdToWallYaw = reduceAngle<>( + hmdYaw - hmdPositionToWallYaw, -M_PI, M_PI ); + + do + { + // Ignore if the wall we encountered is behind us + if ( std::abs( hmdToWallYaw ) >= M_PI / 2 ) + { + LOG( DEBUG ) << "Ignoring turn in opposite " + "direction (angle " + << std::abs( hmdToWallYaw ) << ")"; + break; + } + + // If the closest corner shares a wall with the last + // wall we turned at, turn relative to that corner + bool cornerShared + = m_autoTurnWallActive[circularIndex( + i, true, chaperoneDistances.size() )] + || m_autoTurnWallActive[circularIndex( + i, false, chaperoneDistances.size() )]; + + bool turnLeft = true; + // Turn away from corner + if ( cornerShared ) + { + // Turn left or right depending on which corner + // it is. If we go based on yaw, we could end up + // turning the wrong way if it's large obtuse + // angle and we're facing more towards the + // previous wall than the left. + turnLeft = m_autoTurnWallActive[circularIndex( + i, false, chaperoneDistances.size() )]; + LOG( DEBUG ) << "turning away from shared corner"; + } + // If within m_cordDetanglingAngle degrees of + // 'straight at a wall', start in whatever direction + // will start untangling your cord + else if ( ( std::abs( hmdToWallYaw ) + <= RotationTabController::cordDetangleAngle() ) + && ( std::abs( parent->m_moveCenterTabController + .getHmdYawTotal() ) + > RotationTabController::minCordTangle() ) ) + { + turnLeft = ( parent->m_moveCenterTabController + .getHmdYawTotal() + < 0.0 ); + LOG( DEBUG ) << "turning to detangle cord"; + } + // Turn the closest angle to the wall + else + { + turnLeft = hmdToWallYaw > 0.0; + LOG( DEBUG ) << "turning closest angle to wall"; + } + + LOG( DEBUG ) << "hmd yaw " << hmdYaw + << ", hmd position to wall angle " + << hmdPositionToWallYaw; + LOG( DEBUG ) << "hmd to wall angle " << hmdToWallYaw; + // Positive hmd-to-wall is facing left, negative is + // facing right (relative to the wall) + double delta_degrees + = ( ( turnLeft ? M_PI / 2 : -M_PI / 2 ) - hmdToWallYaw ) + * k_radiansToCentidegrees; + if ( RotationTabController::autoTurnUseCornerAngle() + && cornerShared ) + { + // Turn the angle of the corner + // Three relevant points - the far corner wall + // we're currently touching, the far corner on + // the wall we've just touched, and the corner + // between them + const size_t cornerCnt + = parent->chaperoneUtils().quadsCount(); + const size_t middleCornerIdx + = turnLeft ? i + : circularIndex( i, true, cornerCnt ); + const auto& middleCorner + = parent->chaperoneUtils().getCorner( + middleCornerIdx ); + const auto& newWallCorner + = parent->chaperoneUtils().getCorner( circularIndex( + middleCornerIdx, turnLeft, cornerCnt ) ); + const auto& touchingWallCorner + = parent->chaperoneUtils().getCorner( circularIndex( + middleCornerIdx, !turnLeft, cornerCnt ) ); + + double newWallAngle = static_cast( std::atan2( + middleCorner.v[0] - newWallCorner.v[0], + middleCorner.v[2] - newWallCorner.v[2] ) ); + double touchingWallAngle + = static_cast( std::atan2( + middleCorner.v[0] - touchingWallCorner.v[0], + middleCorner.v[2] - touchingWallCorner.v[2] ) ); + LOG( DEBUG ) << "twa: " << touchingWallAngle + << ", nwa: " << newWallAngle << ", diff: " + << ( newWallAngle - touchingWallAngle ); + delta_degrees = ( newWallAngle - touchingWallAngle + + ( turnLeft ? M_PI : -M_PI ) ) + * k_radiansToCentidegrees; + delta_degrees + = reduceAngle<>( delta_degrees, -18000.0, 18000.0 ); + } + LOG( DEBUG ) << "rotating space " << ( delta_degrees / 100 ) + << " degrees"; + switch ( RotationTabController::autoTurnModeType() ) + { + case AutoTurnModes::SNAP: + parent->m_moveCenterTabController.setRotation( + static_cast( + parent->m_moveCenterTabController.rotation() + + delta_degrees ) ); + break; + case AutoTurnModes::LINEAR_SMOOTH_TURN: + m_autoTurnLinearSmoothTurnRemaining + += static_cast( delta_degrees ); + break; + } + } while ( false ); + + m_autoTurnWallActive[i] = true; + } + else if ( ( chaperoneQuad.distance + > ( RotationTabController::autoTurnActivationDistance() + + RotationTabController:: + autoTurnDeactivationDistance() ) ) + && m_autoTurnWallActive[i] ) + { + m_autoTurnWallActive[i] = false; + } + } + m_autoTurnLastUpdate = currentTime; + } + else + { + m_autoTurnWallActive.clear(); + } +} + +// getters + +bool RotationTabController::autoTurnEnabled() const +{ + return settings::getSetting( + settings::BoolSetting::ROTATION_autoturnEnabled ); +} + +float RotationTabController::autoTurnActivationDistance() const +{ + return static_cast( settings::getSetting( + settings::DoubleSetting::ROTATION_activationDistance ) ); +} + +float RotationTabController::autoTurnDeactivationDistance() const +{ + return static_cast( settings::getSetting( + settings::DoubleSetting::ROTATION_deactivateDistance ) ); +} + +bool RotationTabController::autoTurnUseCornerAngle() const +{ + return settings::getSetting( + settings::BoolSetting::ROTATION_autoturnUseCornerAngle ); +} + +double RotationTabController::cordDetangleAngle() const +{ + return settings::getSetting( + settings::DoubleSetting::ROTATION_cordDetanglingAngle ); +} + +double RotationTabController::minCordTangle() const +{ + return settings::getSetting( + settings::DoubleSetting::ROTATION_autoturnMinCordTangle ); +} + +int RotationTabController::autoTurnSpeed() const +{ + return settings::getSetting( + settings::IntSetting::ROTATION_autoturnLinearTurnSpeed ); +} + +AutoTurnModes RotationTabController::autoTurnModeType() const +{ + return static_cast( + settings::getSetting( settings::IntSetting::ROTATION_autoturnMode ) ); +} + +int RotationTabController::autoTurnMode() const +{ + return settings::getSetting( settings::IntSetting::ROTATION_autoturnMode ); +} + +bool RotationTabController::vestibularMotionEnabled() const +{ + return settings::getSetting( + settings::BoolSetting::ROTATION_autoturnVestibularMotionEnabled ); +} + +double RotationTabController::vestibularMotionRadius() const +{ + return settings::getSetting( + settings::DoubleSetting::ROTATION_autoturnVestibularMotionRadius ); +} + +// Setters + +void RotationTabController::setAutoTurnEnabled( bool value, bool notify ) +{ + settings::setSetting( settings::BoolSetting::ROTATION_autoturnEnabled, + value ); + + if ( notify ) + { + emit autoTurnEnabledChanged( value ); + } +} +void RotationTabController::setAutoTurnActivationDistance( float value, + bool notify ) +{ + settings::setSetting( settings::DoubleSetting::ROTATION_activationDistance, + static_cast( value ) ); + if ( notify ) + { + emit autoTurnActivationDistanceChanged( value ); + } +} +void RotationTabController::setAutoTurnDeactivationDistance( float value, + bool notify ) +{ + settings::setSetting( settings::DoubleSetting::ROTATION_deactivateDistance, + static_cast( value ) ); + if ( notify ) + { + emit autoTurnDeactivationDistanceChanged( value ); + } +} + +void RotationTabController::setAutoTurnUseCornerAngle( bool value, bool notify ) +{ + settings::setSetting( + settings::BoolSetting::ROTATION_autoturnUseCornerAngle, value ); + if ( notify ) + { + emit autoTurnUseCornerAngleChanged( value ); + } +} + +void RotationTabController::setCordDetangleAngle( double value, bool notify ) +{ + settings::setSetting( settings::DoubleSetting::ROTATION_cordDetanglingAngle, + value ); + if ( notify ) + { + emit cordDetangleAngleChanged( value ); + } +} + +void RotationTabController::setMinCordTangle( double value, bool notify ) +{ + settings::setSetting( + settings::DoubleSetting::ROTATION_autoturnMinCordTangle, value ); + if ( notify ) + { + emit minCordTangleChanged( value ); + } +} +void RotationTabController::setAutoTurnSpeed( int value, bool notify ) +{ + settings::setSetting( + settings::IntSetting::ROTATION_autoturnLinearTurnSpeed, value ); + if ( notify ) + { + emit autoTurnSpeedChanged( value ); + } +} +void RotationTabController::setAutoTurnMode( int value, bool notify ) +{ + settings::setSetting( settings::IntSetting::ROTATION_autoturnMode, + static_cast( value ) ); + if ( notify ) + { + emit autoTurnModeChanged( value ); + } +} +void RotationTabController::setVestibularMotionEnabled( bool value, + bool notify ) +{ + settings::setSetting( + settings::BoolSetting::ROTATION_autoturnVestibularMotionEnabled, + value ); + if ( notify ) + { + emit vestibularMotionEnabledChanged( value ); + } +} +void RotationTabController::setVestibularMotionRadius( double value, + bool notify ) +{ + settings::setSetting( + settings::DoubleSetting::ROTATION_autoturnVestibularMotionRadius, + value ); + if ( notify ) + { + emit vestibularMotionRadiusChanged( value ); + } +} + +} // namespace advsettings diff --git a/src/tabcontrollers/RotationTabController.h b/src/tabcontrollers/RotationTabController.h new file mode 100644 index 0000000..f7e9a63 --- /dev/null +++ b/src/tabcontrollers/RotationTabController.h @@ -0,0 +1,217 @@ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "../utils/FrameRateUtils.h" +#include "../utils/ChaperoneUtils.h" +#include "../settings/settings_object.h" +#include "MoveCenterTabController.h" + +class QQuickWindow; +// application namespace +namespace advsettings +{ +// forward declaration +class OverlayController; + +enum class AutoTurnModes +{ + SNAP, + LINEAR_SMOOTH_TURN +}; + +struct RotationProfile : settings::ISettingsObject +{ + std::string profileName; + + // Settings + bool autoTurnEnabled = true; + float activationDistance + = 0.4f; // TODO: Have some indicator in UI that lower values are + // more walking space + float deactivateDistance = 0.15f; + bool autoTurnUseCornerAngle = true; // Turn the exact corner of the + // angle, rather than the headset + double cordDetanglingAngle = 1000 * k_centidegreesToRadians; + double autoTurnMinCordTangle = 2 * M_PI; + int autoTurnLinearTurnSpeed = 9000; // centidegrees/sec + + AutoTurnModes autoTurnMode = AutoTurnModes::LINEAR_SMOOTH_TURN; + + /* 22.0: generally considered imperceptable to humans + * 11.0: light + * 5.5: strong + * 1.0: extreme + */ + bool autoTurnVestibularMotionEnabled = false; + double autoTurnVestibularMotionRadius = 11.0; + + virtual settings::SettingsObjectData saveSettings() const override + { + settings::SettingsObjectData o; + o.addValue( autoTurnEnabled ); + o.addValue( static_cast( activationDistance ) ); + o.addValue( static_cast( deactivateDistance ) ); + o.addValue( autoTurnUseCornerAngle ); + o.addValue( cordDetanglingAngle ); + o.addValue( autoTurnMinCordTangle ); + o.addValue( autoTurnLinearTurnSpeed ); + o.addValue( static_cast( autoTurnMode ) ); + o.addValue( autoTurnVestibularMotionEnabled ); + o.addValue( autoTurnVestibularMotionRadius ); + + return o; + } + + virtual void loadSettings( settings::SettingsObjectData& obj ) override + { + // Settings + autoTurnEnabled = obj.getNextValueOrDefault( true ); + activationDistance + = static_cast( obj.getNextValueOrDefault( 0.4 ) ); + deactivateDistance + = static_cast( obj.getNextValueOrDefault( 0.15 ) ); + autoTurnUseCornerAngle = obj.getNextValueOrDefault( true ); + cordDetanglingAngle + = obj.getNextValueOrDefault( 1000 * k_centidegreesToRadians ); + autoTurnMinCordTangle = obj.getNextValueOrDefault( 2 * M_PI ); + autoTurnLinearTurnSpeed = obj.getNextValueOrDefault( 9000 ); + autoTurnMode = static_cast( obj.getNextValueOrDefault( + static_cast( AutoTurnModes::LINEAR_SMOOTH_TURN ) ) ); + autoTurnVestibularMotionEnabled = obj.getNextValueOrDefault( false ); + autoTurnVestibularMotionRadius = obj.getNextValueOrDefault( 11.0 ); + } + + virtual std::string settingsName() const override + { + return "RotationTabController::RotationProfile"; + } +}; + +class RotationTabController : public QObject +{ + Q_OBJECT + Q_PROPERTY( bool autoTurnEnabled READ autoTurnEnabled WRITE + setAutoTurnEnabled NOTIFY autoTurnEnabledChanged ) + Q_PROPERTY( float autoTurnActivationDistance READ autoTurnActivationDistance + WRITE setAutoTurnActivationDistance NOTIFY + autoTurnActivationDistanceChanged ) + Q_PROPERTY( + float autoTurnDeactivationDistance READ autoTurnDeactivationDistance + WRITE setAutoTurnDeactivationDistance NOTIFY + autoTurnDeactivationDistanceChanged ) + Q_PROPERTY( + bool autoTurnUseCornerAngle READ autoTurnUseCornerAngle WRITE + setAutoTurnUseCornerAngle NOTIFY autoTurnUseCornerAngleChanged ) + Q_PROPERTY( double cordDetangleAngle READ cordDetangleAngle WRITE + setCordDetangleAngle NOTIFY cordDetangleAngleChanged ) + Q_PROPERTY( double minCordTangle READ minCordTangle WRITE setMinCordTangle + NOTIFY minCordTangleChanged ) + Q_PROPERTY( int autoTurnSpeed READ autoTurnSpeed WRITE setAutoTurnSpeed + NOTIFY autoTurnSpeedChanged ) + Q_PROPERTY( int autoTurnMode READ autoTurnMode WRITE setAutoTurnMode NOTIFY + autoTurnModeChanged ) + Q_PROPERTY( + bool vestibularMotionEnabled READ vestibularMotionEnabled WRITE + setVestibularMotionEnabled NOTIFY vestibularMotionEnabledChanged ) + Q_PROPERTY( + double vestibularMotionRadius READ vestibularMotionRadius WRITE + setVestibularMotionRadius NOTIFY vestibularMotionRadiusChanged ) + +private: + OverlayController* parent; + + // Variables + int m_autoTurnLinearSmoothTurnRemaining = 0; + std::chrono::steady_clock::time_point m_autoTurnLastUpdate; + std::vector m_autoTurnWallActive; + vr::HmdMatrix34_t m_autoTurnLastHmdUpdate; + std::vector m_autoTurnChaperoneDistancesLast; + double m_autoTurnRoundingError = 0.0; + + bool m_isHMDActive = false; + + void doAutoTurn( + const vr::TrackedDevicePose_t& poseHmd, + const std::vector& chaperoneDistances ); + void doVestibularMotion( + const vr::TrackedDevicePose_t& poseHmd, + const std::vector& chaperoneDistances ); + +public: + void initStage1(); + void initStage2( OverlayController* parent ); + + void eventLoopTick( vr::TrackedDevicePose_t* devicePoses ); + + float boundsVisibility() const; + + bool autoTurnEnabled() const; + float autoTurnActivationDistance() const; + float autoTurnDeactivationDistance() const; + bool autoTurnUseCornerAngle() const; + double cordDetangleAngle() const; + double minCordTangle() const; + int autoTurnSpeed() const; + AutoTurnModes autoTurnModeType() const; + int autoTurnMode() const; + bool vestibularMotionEnabled() const; + double vestibularMotionRadius() const; + +public slots: + void setAutoTurnEnabled( bool value, bool notify = true ); + void setAutoTurnActivationDistance( float value, bool notify = true ); + void setAutoTurnDeactivationDistance( float value, bool notify = true ); + void setAutoTurnUseCornerAngle( bool value, bool notify = true ); + void setCordDetangleAngle( double value, bool notify = true ); + void setMinCordTangle( double value, bool notify = true ); + void setAutoTurnSpeed( int value, bool notify = true ); + void setAutoTurnMode( int value, bool notify = true ); + void setVestibularMotionEnabled( bool value, bool notify = true ); + void setVestibularMotionRadius( double value, bool notify = true ); + +signals: + + void autoTurnEnabledChanged( bool value ); + void autoTurnActivationDistanceChanged( float value ); + void autoTurnDeactivationDistanceChanged( float value ); + void autoTurnUseCornerAngleChanged( bool value ); + void cordDetangleAngleChanged( double value ); + void minCordTangleChanged( double value ); + void autoTurnSpeedChanged( int value ); + void autoTurnModeChanged( int value ); + void vestibularMotionEnabledChanged( bool value ); + void vestibularMotionRadiusChanged( double value ); +}; + +// Would be nice to do but the standard doesn't allow +// for floating point non-types. +template inline T reduceAngle( T angle, T min, T max ) +{ + while ( angle >= max ) + { + angle -= ( max - min ); + } + while ( angle < min ) + { + angle += ( max - min ); + } + return angle; +} + +// Convienience function for incrementing/decrementing an index in a circular +// buffer. +inline size_t circularIndex( const size_t idx, + const bool increment, + const size_t modulus ) noexcept +{ + return increment ? ( ( idx + 1 ) % modulus ) + : ( ( idx == 0 ) ? ( modulus - 1 ) : ( idx - 1 ) ); +} + +} // namespace advsettings diff --git a/src/utils/ChaperoneUtils.cpp b/src/utils/ChaperoneUtils.cpp index 63d25a6..884f1c3 100644 --- a/src/utils/ChaperoneUtils.cpp +++ b/src/utils/ChaperoneUtils.cpp @@ -4,11 +4,10 @@ namespace utils { -float ChaperoneUtils::_getDistanceToChaperone( - const vr::HmdVector3_t& x, - vr::HmdVector3_t* projectedPoint ) +std::vector + ChaperoneUtils::_getDistancesToChaperone( const vr::HmdVector3_t& x ) { - float distance = NAN; + std::vector result; vr::HmdVector3_t* _cornersPtr = _corners.get(); for ( uint32_t i = 0; i < _quadsCount; i++ ) { @@ -60,18 +59,14 @@ float ChaperoneUtils::_getDistanceToChaperone( d = static_cast( sqrt( static_cast( d_x * d_x + d_z * d_z ) ) ); } - if ( std::isnan( distance ) || d < distance ) - { - distance = d; - if ( projectedPoint ) - { - projectedPoint->v[0] = x1_x; - projectedPoint->v[1] = x.v[1]; - projectedPoint->v[2] = x1_z; - } - } + ChaperoneQuadData computedQuad; + computedQuad.distance = d; + computedQuad.nearestPoint = { x1_x, x.v[1], x1_z }; + computedQuad.corners[0] = r0; + computedQuad.corners[1] = r1; + result.push_back( computedQuad ); } - return distance; + return result; } void ChaperoneUtils::loadChaperoneData( bool fromLiveBounds ) diff --git a/src/utils/ChaperoneUtils.h b/src/utils/ChaperoneUtils.h index 636c344..0eff735 100644 --- a/src/utils/ChaperoneUtils.h +++ b/src/utils/ChaperoneUtils.h @@ -3,9 +3,28 @@ #include #include #include +#include +#include +#include namespace utils { +struct ChaperoneQuadData +{ + float distance; + vr::HmdVector3_t nearestPoint; + vr::HmdVector3_t corners[2]; + + const vr::HmdVector3_t& closestCorner( const vr::HmdVector3_t& point ) const + { + auto cornerDistanceA = std::pow( point.v[0] - corners[0].v[0], 2.0 ) + + std::pow( point.v[2] - corners[0].v[2], 2.0 ); + auto cornerDistanceB = std::pow( point.v[0] - corners[1].v[0], 2.0 ) + + std::pow( point.v[2] - corners[1].v[2], 2.0 ); + return ( cornerDistanceA < cornerDistanceB ) ? corners[0] : corners[1]; + } +}; + class ChaperoneUtils { private: @@ -13,11 +32,14 @@ private: uint32_t _quadsCount = 0; std::unique_ptr _corners; bool _chaperoneWellFormed = true; - - float _getDistanceToChaperone( const vr::HmdVector3_t& point, - vr::HmdVector3_t* projectedPoint ); + std::vector + _getDistancesToChaperone( const vr::HmdVector3_t& point ); public: + const vr::HmdVector3_t& getCorner( size_t i ) const noexcept + { + return ( _corners.get() )[i]; + } uint32_t quadsCount() const noexcept { return _quadsCount; @@ -34,20 +56,41 @@ public: void loadChaperoneData( bool fromLiveBounds = true ); - float getDistanceToChaperone( const vr::HmdVector3_t& point, - vr::HmdVector3_t* projectedPoint = nullptr, - bool doLock = false ) + std::vector + getDistancesToChaperone( const vr::HmdVector3_t& point, + bool doLock = false ) { if ( doLock ) { std::lock_guard lock( _mutex ); - return _getDistanceToChaperone( point, projectedPoint ); + return _getDistancesToChaperone( point ); } else { - return _getDistanceToChaperone( point, projectedPoint ); + return _getDistancesToChaperone( point ); } } + + ChaperoneQuadData getDistanceToChaperone( const vr::HmdVector3_t& point, + bool doLock = false ) + { + auto distances = getDistancesToChaperone( point, doLock ); + if ( distances.empty() ) + { + ChaperoneQuadData ret; + ret.distance = NAN; + return ret; + } + + return *std::min_element( distances.begin(), + distances.end(), + []( const ChaperoneQuadData& quadA, + const ChaperoneQuadData& quadB ) { + return std::isnan( quadA.distance ) + || ( quadA.distance + < quadB.distance ); + } ); + } }; } // end namespace utils