From 1d66ae87bfd5d42aca52424120c34dde35ba829f Mon Sep 17 00:00:00 2001 From: iChris4 Date: Wed, 23 Sep 2026 02:04:22 +0200 Subject: [PATCH] Sit in the cockpit with hands on the wheel by default First person already seats you in the cockpit, so hand steering belongs with it: the stick keeps steering until a grip actually takes hold of the wheel, and nothing else changes until it does. "yaw_pitch" becomes the default anchor for the same reason - from the driver's seat the vehicle's own climb reads as the ground rising, where a level horizon reads as the kart sinking away. Both defaults move together in runtime_config.h, the fresh-config template, the F10 menu's reset, and both launchers, which mirror the runtime's constants. The menu's reset also puts the rotation combo back on the default rather than on "yaw", and now covers hand steering with the other cockpit keys. Co-Authored-By: Claude Opus 5 --- OPENXR.md | 22 +++++++++++-------- README.md | 4 ++-- .../quest/launcher/SettingsPage.kt | 14 +++++++----- runtime/include/runtime_config.h | 17 ++++++++------ runtime/src/settings_overlay.cpp | 11 ++++++---- 5 files changed, 41 insertions(+), 27 deletions(-) diff --git a/OPENXR.md b/OPENXR.md index eada398..50666e8 100644 --- a/OPENXR.md +++ b/OPENXR.md @@ -57,10 +57,10 @@ first_person_head_forward_meters = 0.0 first_person_head_right_meters = 0.0 first_person_hide_driver = true first_person_hidden_model = 0 -first_person_rotation = "yaw" +first_person_rotation = "yaw_pitch" steering_wheel = true native_steering_wheel = true -hand_steering = false +hand_steering = true performance_level = "boost" ``` @@ -349,15 +349,18 @@ own per-eye delta. The kart's *physics* pose is used deliberately, not the anima animated frame would bob and lurch the camera. `first_person_rotation` decides where the view's orientation comes from, mirroring DolphinXR's -camera-anchor modes. `"yaw"`, the default, keeps the horizon level through a chase-camera tilt or a -banked corner. `"yaw_pitch"` adds the kart's climb, so a slope or a wheelie tips the view while a -banked corner still never rolls it. `"full"` takes the kart's whole orientation, banking included. +camera-anchor modes. `"yaw"` keeps the horizon level through a chase-camera tilt or a banked corner. +`"yaw_pitch"`, the default, adds the kart's climb, so a slope or a wheelie tips the view while a +banked corner still never rolls it: sitting in the cockpit, the vehicle's own climb reads as the +ground rising rather than as the view tipping. `"full"` takes the kart's whole orientation, banking included. All three are the same construction from a forward and an up axis, differing only in which pair they take: pairing a forward with world up is what removes roll. The headset always adds free look on top of whichever is chosen, and only the translation onto the head is common to all three. In the cockpit, `"yaw"` takes the kart's own driving direction rather than the chase camera's -lagging heading, from the level seat frame. With the custom seat, the head's place in the kart is +lagging heading, from the level seat frame, which also damps a damage spin. `"yaw_pitch"`, the +default, and `"full"` take the kart's live orientation about that same seat, keeping only its +stabilised position, so a wheelie, a slope or a spin moves the view with the vehicle. With the custom seat, the head's place in the kart is `first_person_head_up_meters` and its two companions, measured in the kart's own frame; the F10 sliders exist because the comfortable value is a matter of taste and is best judged from inside the headset. @@ -424,9 +427,10 @@ copied array, those that bound one outside the window it was set for, the matche closest position matrix was from the expected one; the first such line with a bound draw also prints both matrices. -**Hand steering.** `hand_steering = true` (off by default; also in WheelWizard's OpenXR VR settings -and the Quest launcher's Settings > VR, beside the seat) -lets you take hold of the wheel or handlebar with the tracked controllers. Squeeze a grip near it: +**Hand steering.** `hand_steering` (on by default, and in WheelWizard's OpenXR VR settings and the +Quest launcher's Settings > VR, beside the seat) +lets you take hold of the wheel or handlebar with the tracked controllers. It costs nothing until a +grip actually takes hold: until then the stick steers as it always has. Squeeze a grip near it: past 55 % squeeze, within `wheel_grab_distance` metres of its plane (default 0.35) and near the rim, or near a bar end, scaled by `wheel_grab_assist`. Once taken, only letting go of the grip releases it. One hand steers by its angle around the hub; two hands steer by the line between them, so leaning diff --git a/README.md b/README.md index 4a8dbdf..a6a66bc 100644 --- a/README.md +++ b/README.md @@ -55,8 +55,8 @@ build, without CPU readback. Menus and unsupported scenes appear as a head-locked virtual screen; a validated single-camera race switches to immersive stereo rendering. VR is opt-in and falls back to the normal desktop renderer if the runtime or headset is unavailable. In first person you sit in the cockpit, where the steering wheel -or handlebar turns with your steering, and optional hand steering by heurazy lets you grab it with -the tracked controllers and turn it. See [`OPENXR.md`](OPENXR.md) for setup, configuration, and the +or handlebar turns with your steering, and hand steering by heurazy lets you grab it with the +tracked controllers and turn it. See [`OPENXR.md`](OPENXR.md) for setup, configuration, and the current limitations. **Music ducking.** diff --git a/android/app/src/main/java/org/wiicompiled/quest/launcher/SettingsPage.kt b/android/app/src/main/java/org/wiicompiled/quest/launcher/SettingsPage.kt index 9b5859a..9767565 100644 --- a/android/app/src/main/java/org/wiicompiled/quest/launcher/SettingsPage.kt +++ b/android/app/src/main/java/org/wiicompiled/quest/launcher/SettingsPage.kt @@ -127,7 +127,7 @@ class SettingsPage( choice( R.string.vr_rotation, R.string.vr_rotation_helper, listOf(R.string.vr_rotation_yaw, R.string.vr_rotation_yaw_pitch, R.string.vr_rotation_full), - read = { stringIndex(it, "vr", "first_person_rotation", ROTATIONS) }, + read = { stringIndex(it, "vr", "first_person_rotation", ROTATIONS, ROTATION_DEFAULT) }, write = { c, index -> c.setString("vr", "first_person_rotation", ROTATIONS[index]) }, enabledIf = firstPerson, ) @@ -160,10 +160,10 @@ class SettingsPage( write = { c, index -> c.setString("vr", "first_person_seat", SEATS[index]) }, enabledIf = firstPerson, ) - // heurazy's grab-and-turn wheel: runtime_config.h's kVrHandSteeringDefault is off. + // heurazy's grab-and-turn wheel: runtime_config.h's kVrHandSteeringDefault is on. toggle( R.string.vr_hand_steering, R.string.vr_hand_steering_helper, - read = { it.bool("vr", "hand_steering") ?: false }, + read = { it.bool("vr", "hand_steering") ?: true }, write = { c, value -> c.setBool("vr", "hand_steering", value) }, enabledIf = cockpit, ) @@ -703,6 +703,9 @@ class SettingsPage( private companion object { val ROTATIONS = listOf("yaw", "yaw_pitch", "full") + + /** runtime_config.h's kVrFirstPersonRotationDefault. */ + val ROTATION_DEFAULT = ROTATIONS.indexOf("yaw_pitch") // The runtime's default ("cockpit") first. val SEATS = listOf("cockpit", "custom") // The runtime's default ("boost") first: an absent key reads as index 0. @@ -726,8 +729,9 @@ class SettingsPage( config.number(section, key)?.takeIf { it in min..max } ?: default /** Unrecognised strings fall back to the first (default) option, as in the runtime. */ - fun stringIndex(config: TomlConfig, section: String, key: String, values: List): Int = - values.indexOf(config.string(section, key)).coerceAtLeast(0) + fun stringIndex(config: TomlConfig, section: String, key: String, values: List, + default: Int = 0): Int = + values.indexOf(config.string(section, key)).takeIf { it >= 0 } ?: default fun resolution(config: TomlConfig): Double = config.number("video", "resolution_multiplier")?.takeIf { it in SUPPORTED_RESOLUTIONS } ?: 1.0 diff --git a/runtime/include/runtime_config.h b/runtime/include/runtime_config.h index 91ab03d..836eeb7 100644 --- a/runtime/include/runtime_config.h +++ b/runtime/include/runtime_config.h @@ -201,8 +201,10 @@ inline constexpr float kVrFirstPersonHeadRightDefault = 0.0f; inline constexpr bool kVrFirstPersonHideDriverDefault = true; inline constexpr int32_t kVrFirstPersonHiddenModelDefault = 0; inline constexpr float kVrFirstPersonHeadOffsetLimit = 10.0f; -// "yaw", "yaw_pitch" or "full", matching FirstPersonRotation. -inline constexpr const char* kVrFirstPersonRotationDefault = "yaw"; +// "yaw", "yaw_pitch" or "full", matching FirstPersonRotation. The cockpit seat +// is the first-person default, and sitting in the vehicle reads better with its +// climb than with a level horizon, so "yaw_pitch" is the default anchor. +inline constexpr const char* kVrFirstPersonRotationDefault = "yaw_pitch"; inline bool IsSupportedVrFirstPersonRotation(std::string_view value) { return value == "yaw" || value == "yaw_pitch" || value == "full"; @@ -221,11 +223,12 @@ inline constexpr float kVrCockpitUnitsPerMeterMax = 400.0f; // The vehicle's steering wheel or handlebar turns with the steering; the // vehicle's own model is animated unless native_steering_wheel is off, which // draws a separate VR wheel instead. Hand steering (grabbing that wheel with -// the tracked controllers, by heurazy) is opt-in. The WheelWizard VR launcher -// registers hand_steering with this same default. +// the tracked controllers, by heurazy) comes with it: the stick still steers +// until a grip actually takes hold of the wheel. Both launchers register +// hand_steering with this same default. inline constexpr bool kVrSteeringWheelDefault = true; inline constexpr bool kVrNativeSteeringWheelDefault = true; -inline constexpr bool kVrHandSteeringDefault = false; +inline constexpr bool kVrHandSteeringDefault = true; // Hand steering tuning ranges; the defaults are mkw::vr::WheelTuning's. inline constexpr float kVrWheelDegreesMin = 20.0f, kVrWheelDegreesMax = 180.0f; inline constexpr float kVrWheelGrabDistanceMin = 0.15f, kVrWheelGrabDistanceMax = 0.8f; @@ -531,7 +534,7 @@ inline void EnsureConfigFile() { "# Where the view's orientation comes from: \"yaw\" levels the\n" "# horizon, \"yaw_pitch\" adds the kart's climb but no roll, and\n" "# \"full\" takes the kart's whole orientation so the view banks.\n" - "first_person_rotation = \"yaw\"\n" + "first_person_rotation = \"yaw_pitch\"\n" "# In the cockpit the vehicle's steering wheel or handlebar turns\n" "# with the steering. native_steering_wheel animates the vehicle's\n" "# own model; false draws a separate VR wheel instead.\n" @@ -545,7 +548,7 @@ inline void EnsureConfigFile() { "# the wheel, how quickly the wheel follows the hands, how long\n" "# (seconds) a hand that loses tracking keeps hold, and a short\n" "# pulse on grab and release. All changeable live from the F10 menu.\n" - "hand_steering = false\n" + "hand_steering = true\n" "wheel_kart_degrees = 90.0\n" "wheel_bike_degrees = 45.0\n" "wheel_grab_distance = 0.35\n" diff --git a/runtime/src/settings_overlay.cpp b/runtime/src/settings_overlay.cpp index 7690e51..5e32635 100644 --- a/runtime/src/settings_overlay.cpp +++ b/runtime/src/settings_overlay.cpp @@ -207,15 +207,15 @@ int g_vrControllerMode = [] { return 0; }(); constexpr std::array kVrFirstPersonRotationNames{"yaw", "yaw_pitch", "full"}; -int g_vrFirstPersonRotation = [] { - const std::string mode = RuntimeConfigFile::VrFirstPersonRotation(); +int VrFirstPersonRotationIndex(std::string_view mode) { for (size_t i = 0; i < kVrFirstPersonRotationNames.size(); ++i) { if (mode == kVrFirstPersonRotationNames[i]) { return static_cast(i); } } return 0; -}(); +} +int g_vrFirstPersonRotation = VrFirstPersonRotationIndex(RuntimeConfigFile::VrFirstPersonRotation()); // SDL_SCANCODE_UNKNOWN means unbound, which is also what an unrecognised // name in the config file resolves to rather than silently picking a key. SDL_Scancode g_vrRecenterScancode = [] { @@ -1618,17 +1618,20 @@ void DrawVrSettings() { g_vrCockpitUnitsPerMeter = RuntimeConfigFile::kVrCockpitUnitsPerMeterDefault; g_vrSteeringWheel = RuntimeConfigFile::kVrSteeringWheelDefault; g_vrNativeSteeringWheel = RuntimeConfigFile::kVrNativeSteeringWheelDefault; + g_vrHandSteering = RuntimeConfigFile::kVrHandSteeringDefault; RuntimeConfigFile::SetVrFirstPersonSeat(RuntimeConfigFile::kVrFirstPersonSeatDefault); RuntimeConfigFile::SetVrCockpitUnitsPerMeter(g_vrCockpitUnitsPerMeter); RuntimeConfigFile::SetVrSteeringWheel(g_vrSteeringWheel); RuntimeConfigFile::SetVrNativeSteeringWheel(g_vrNativeSteeringWheel); + RuntimeConfigFile::SetVrHandSteering(g_vrHandSteering); g_vrFirstPersonUnitsPerMeter = RuntimeConfigFile::kVrFirstPersonUnitsPerMeterDefault; g_vrFirstPersonHeadUp = RuntimeConfigFile::kVrFirstPersonHeadUpDefault; g_vrFirstPersonHeadForward = RuntimeConfigFile::kVrFirstPersonHeadForwardDefault; g_vrFirstPersonHeadRight = RuntimeConfigFile::kVrFirstPersonHeadRightDefault; g_vrFirstPersonHideDriver = RuntimeConfigFile::kVrFirstPersonHideDriverDefault; g_vrFirstPersonHiddenModel = RuntimeConfigFile::kVrFirstPersonHiddenModelDefault; - g_vrFirstPersonRotation = 0; + g_vrFirstPersonRotation = + VrFirstPersonRotationIndex(RuntimeConfigFile::kVrFirstPersonRotationDefault); RuntimeConfigFile::SetVrFirstPersonRotation( RuntimeConfigFile::kVrFirstPersonRotationDefault); RuntimeConfigFile::SetVrFirstPersonUnitsPerMeter(g_vrFirstPersonUnitsPerMeter);