Implement Auto-turn and Redirected Walking

Initial implementation of Auto-Turn

Auto-turn leverages Space Turn automatically whenever you reach a configured distance from your chaperone boundary. Turn direction is in order of precedence:

1. Away from a corner where you're already touching one wall. This is to avoid being turned to face a wall, which we pretty much never want. If 'use corner angle' is enabled, it'll turn the angle of the corner, otherwise it'll turn the angle of your headset to the wall you encountered.
2. In whichever direction will start you unwinding your cord. If your rotations is greater than 'cord detangling rotations' and your angle to the wall is less than 'min detangling angle' it'll turn you such that the thing you're walking towards is directly along a wall, in the direction which will begin unwinding your cord.
3. In the closest angle to the wall. This too will put whatever you're walking towards directly along a wall, so you can walk the maximum distance to it. 
- If Smooth turn is checked, it'll turn at a constant (configurable) rate until the world is aligned with the new angle, otherwise it'll snap to the new direction instantly. 
- Activation distance is the distance from your chaperone at which auto-turn starts, and deactivation distance is the minimum distance (+ the activation distance) at which it'll be able to be activated again, to avoid turning twice at once.


Redirected walking tries to gently guide you away from your playspace boundaries by tweaking the world's angle at smaller increments than your brain is capable of noticing. While most playspaces won't be big enough to allow this to give you a true 'infinite walk' experience, it can give the perception of your playspace being ever so slightly bigger by making you walk in a slight arc in real life while walking forward in VR.
- Redirected walking is independent from Auto-turn and can be activated on its own. At both the 'slight' and 'imperceptable' radii, the turning speed is incredibly minimal. When walking along one wall of your chaperone to another, you should notice the far corner of your playspace (relative to the world) drifting ever so slightly away from you. In normal circumstances at the lowest settings this shouldn't be noticable. 


Co-authored-by: feilen <feilen1000@gmail.com>

* Initial implementation of Infinite Walk

* Code cleanup and abstraction, use nearest corner instead of nearest wall to determine turn direction

* Run clang-format

* Turn relative to the last wall if we run into adjacent walls, otherwise turn at easiest angle

* Limit maximum turns (just refuse to turn for now), only turn on shared corners if going the same direction

* Improve same-corner

* get rid of LastDirectionLeft

* WIP: cord untangling hint

* Try to automatically untangle cord, don't rotate if we encounter a wall behind us

* Incorrect offset for walls

* optimize if hmd inactive

* Cleanup/clarify angles

* (probably?) solve rare segfault

* Adjust the defaults and fix a bug with blocking reverse turns

* Implement smooth turn, fix a bug with shared-corners that would cause it to turn the wrong way

* actually really fix shared corner bug

vect[i + 1 % vect.size()] was somehow evaluating to vect[i + (1 %
vect.size())]

* Move configurables for autoturn to .h to make clearer what's configurable

* WIP: true redirected walking

* First pass optimization/cleanup

* Second pass cleanup/optimize

* Missing clang-format

* Somewhat ugly: Optionally turn the exact angle of the corner when using shared corner turns

* added rotation tab controller in progress

* cleaned up chaperone controller/ finished settings/rotationtab

* did first 4 settings qml, and main qml setup

* initial working build, not all settings UI done

* merging master into redirected walking. (#387)

* Update OVR to 1.10.30 (#379)

* updated to ovr 1.10.30

* fixed updated showoverlaykeyboard call

* fixed keyboard location

* Updated Manifest (#380)

* updated to ovr 1.10.30

* fixed updated showoverlaykeyboard call

* fixed keyboard location

* removed un-necessary matrix, updated app key and manifest

* fixed formatting

* Fix push to mute icon not being used when PTM enabled

* added touch default bindings (#385)

travis CI down/broken?

Co-authored-by: Vasily <vautkin@teknik.io>

* added action, and additional settings

* fixed various bugs added turn speed and mode

* fixed conversion error for turn speed

* added redirected walking options

* formatting

* formatting fixing

* removed old comments

* changed logs to debug and some wording changes

* changed UI layout

* added space turn to rotation page

* added icon, fixed autoturn to autoTurn, fixed conversion to be in qml vs set

* Update src/res/qml/RootPage.qml

yep XD

Co-Authored-By: Kung-vr <38249782+Kung-vr@users.noreply.github.com>

* reverted build changes

* removed un-needed app image build commands

* Cleanup some functions, avoid an extra chaperone check.

* Make redirected walking and autoturn independently enable-able

Co-authored-by: Feilen <feilen1000@gmail.com>
Co-authored-by: Vasily <vautkin@teknik.io>
Co-authored-by: Kung-vr <38249782+Kung-vr@users.noreply.github.com>
This commit is contained in:
ykeara
2020-04-08 18:57:53 -07:00
committed by GitHub
co-authored by Vasily Kung-vr Feilen
parent c0d44812d6
commit 1ae740463c
26 changed files with 1483 additions and 41 deletions
+4 -2
View File
@@ -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
+4
View File
@@ -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 \
+5
View File
@@ -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()
{
+3
View File
@@ -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;
+23
View File
@@ -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<SteamVRTabController>(
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() )
{
+3
View File
@@ -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 );
+6
View File
@@ -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",
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;}
.st1{fill:#FFFFFF;}
</style>
<path class="st0" d="M0,0h24v24H0V0z"/>
<path class="st1" d="M15.6,5.6L11,1v3.1C7.1,4.6,4,7.9,4,12s3.1,7.4,7,7.9v-2C8.2,17.4,6,15,6,12s2.2-5.4,5-5.9V10L15.6,5.6z
M19.9,11c-0.2-1.4-0.7-2.7-1.6-3.9l-1.4,1.4c0.5,0.8,0.9,1.6,1,2.5H19.9z M13,17.9v2c1.4-0.2,2.7-0.7,3.9-1.6l-1.4-1.4
C14.7,17.4,13.9,17.8,13,17.9z M16.9,15.5l1.4,1.4c0.9-1.2,1.5-2.5,1.6-3.9h-2C17.8,13.9,17.4,14.7,16.9,15.5z"/>
</svg>

After

Width:  |  Height:  |  Size: 827 B

+11
View File
@@ -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
+6
View File
@@ -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
-5
View File
@@ -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
@@ -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
}
}
}
@@ -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))
}
}
}
@@ -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))
}
}
}
+1
View File
@@ -29,5 +29,6 @@
<file alias="steamvr_tab_icon">img/main_menu_icons/steamvr_tab_icon.svg</file>
<file alias="utilities_tab_icon">img/main_menu_icons/utilities_tab_icon.svg</file>
<file alias="video_tab_icon">img/main_menu_icons/video_tab_icon.svg</file>
<file alias="rotation_tab_icon">img/main_menu_icons/rotation_tab_icon.svg</file>
</qresource>
</RCC>
@@ -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 },
};
};
@@ -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";
}
+16 -3
View File
@@ -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();
+10 -8
View File
@@ -2,6 +2,8 @@
#include <QQuickWindow>
#include "../overlaycontroller.h"
#include "../settings/settings.h"
#include "../utils/Matrix.h"
#include "../quaternion/quaternion.h"
#include <cmath>
// 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;
}
}
}
@@ -8,7 +8,9 @@
#include <openvr.h>
#include <cmath>
#include "../utils/FrameRateUtils.h"
#include "../utils/ChaperoneUtils.h"
#include "../settings/settings_object.h"
#include "MoveCenterTabController.h"
class QQuickWindow;
// application namespace
@@ -0,0 +1,553 @@
#include "RotationTabController.h"
#include <QQuickWindow>
#include "../overlaycontroller.h"
#include "../settings/settings.h"
#include "../utils/Matrix.h"
#include "../quaternion/quaternion.h"
#include <cmath>
// 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<std::recursive_mutex> 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<utils::ChaperoneQuadData>& 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<double>(
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<int>( 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<utils::ChaperoneQuadData>& 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<std::chrono::milliseconds>(
currentTime - m_autoTurnLastUpdate )
.count();
auto miniDeltaAngle = static_cast<int>(
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<int>( 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<double>(
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<double>( std::atan2(
middleCorner.v[0] - newWallCorner.v[0],
middleCorner.v[2] - newWallCorner.v[2] ) );
double touchingWallAngle
= static_cast<double>( 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<int>(
parent->m_moveCenterTabController.rotation()
+ delta_degrees ) );
break;
case AutoTurnModes::LINEAR_SMOOTH_TURN:
m_autoTurnLinearSmoothTurnRemaining
+= static_cast<int>( 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<float>( settings::getSetting(
settings::DoubleSetting::ROTATION_activationDistance ) );
}
float RotationTabController::autoTurnDeactivationDistance() const
{
return static_cast<float>( 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<AutoTurnModes>(
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<double>( value ) );
if ( notify )
{
emit autoTurnActivationDistanceChanged( value );
}
}
void RotationTabController::setAutoTurnDeactivationDistance( float value,
bool notify )
{
settings::setSetting( settings::DoubleSetting::ROTATION_deactivateDistance,
static_cast<double>( 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<int>( 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
+217
View File
@@ -0,0 +1,217 @@
#pragma once
#include <QObject>
#include <memory>
#include <chrono>
#include <thread>
#include <openvr.h>
#include <cmath>
#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<double>( activationDistance ) );
o.addValue( static_cast<double>( deactivateDistance ) );
o.addValue( autoTurnUseCornerAngle );
o.addValue( cordDetanglingAngle );
o.addValue( autoTurnMinCordTangle );
o.addValue( autoTurnLinearTurnSpeed );
o.addValue( static_cast<int>( autoTurnMode ) );
o.addValue( autoTurnVestibularMotionEnabled );
o.addValue( autoTurnVestibularMotionRadius );
return o;
}
virtual void loadSettings( settings::SettingsObjectData& obj ) override
{
// Settings
autoTurnEnabled = obj.getNextValueOrDefault( true );
activationDistance
= static_cast<float>( obj.getNextValueOrDefault( 0.4 ) );
deactivateDistance
= static_cast<float>( 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<AutoTurnModes>( obj.getNextValueOrDefault(
static_cast<int>( 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<bool> m_autoTurnWallActive;
vr::HmdMatrix34_t m_autoTurnLastHmdUpdate;
std::vector<utils::ChaperoneQuadData> m_autoTurnChaperoneDistancesLast;
double m_autoTurnRoundingError = 0.0;
bool m_isHMDActive = false;
void doAutoTurn(
const vr::TrackedDevicePose_t& poseHmd,
const std::vector<utils::ChaperoneQuadData>& chaperoneDistances );
void doVestibularMotion(
const vr::TrackedDevicePose_t& poseHmd,
const std::vector<utils::ChaperoneQuadData>& 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 <typename T, T min, T max> but the standard doesn't allow
// for floating point non-types.
template <typename T> 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
+10 -15
View File
@@ -4,11 +4,10 @@
namespace utils
{
float ChaperoneUtils::_getDistanceToChaperone(
const vr::HmdVector3_t& x,
vr::HmdVector3_t* projectedPoint )
std::vector<ChaperoneQuadData>
ChaperoneUtils::_getDistancesToChaperone( const vr::HmdVector3_t& x )
{
float distance = NAN;
std::vector<ChaperoneQuadData> 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<float>(
sqrt( static_cast<double>( 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 )
+51 -8
View File
@@ -3,9 +3,28 @@
#include <memory>
#include <mutex>
#include <openvr.h>
#include <cmath>
#include <vector>
#include <algorithm>
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<vr::HmdVector3_t> _corners;
bool _chaperoneWellFormed = true;
float _getDistanceToChaperone( const vr::HmdVector3_t& point,
vr::HmdVector3_t* projectedPoint );
std::vector<ChaperoneQuadData>
_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<ChaperoneQuadData>
getDistancesToChaperone( const vr::HmdVector3_t& point,
bool doLock = false )
{
if ( doLock )
{
std::lock_guard<std::recursive_mutex> 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