Add placeholder steering wheel option for VR driving

This commit is contained in:
iChris4 committed 2026-09-30 02:40:36 +02:00
1 parent ebc97ef9ce
commit 85fab2fa09
10 files changed
+101 -22

No files matched your search

+23 -6
View File
@@ -76,6 +76,7 @@ struct RuntimeUserConfig {
std::optional<float> vrCockpitUnitsPerMeter;
std::optional<bool> vrSteeringWheel;
std::optional<bool> vrNativeSteeringWheel;
std::optional<bool> vrPlaceholderSteeringWheel;
std::optional<bool> vrObjectCulling;
std::optional<bool> vrHandSteering;
std::optional<std::string> vrCockpitItemHand;
@@ -231,13 +232,17 @@ inline constexpr float kVrCockpitUnitsPerMeterDefault = 100.0f;
inline constexpr float kVrCockpitUnitsPerMeterMin = 20.0f;
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) 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.
// vehicle's own model is animated unless native_steering_wheel is off.
// placeholder_steering_wheel draws a separate VR wheel or handlebar whenever
// the vehicle's own is not the one turning (native_steering_wheel off, or a
// draw that does not take the animated copy); off, the vehicle's own is all
// there is. Hand steering (grabbing that wheel with 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 kVrPlaceholderSteeringWheelDefault = false;
inline constexpr bool kVrHandSteeringDefault = true;
inline constexpr const char* kVrCockpitItemHandDefault = "left";
// A quick swing of the item hand forward or back throws the item that way.
@@ -594,9 +599,11 @@ inline void EnsureConfigFile() {
"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"
"# own model. placeholder_steering_wheel draws a separate VR wheel\n"
"# or handlebar whenever the vehicle's own is not the one turning.\n"
"steering_wheel = true\n"
"native_steering_wheel = true\n"
"placeholder_steering_wheel = false\n"
"# The game hides karts and objects its own camera cannot see.\n"
"# object_culling = false draws them anyway, so a head turn or a\n"
"# look over the shoulder shows them; it costs GPU time.\n"
@@ -887,6 +894,7 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
readRangedFloat("cockpit_units_per_meter", kVrCockpitUnitsPerMeterMin, kVrCockpitUnitsPerMeterMax);
config.vrSteeringWheel = FindConfigValue<bool>(document, "vr", "steering_wheel");
config.vrNativeSteeringWheel = FindConfigValue<bool>(document, "vr", "native_steering_wheel");
config.vrPlaceholderSteeringWheel = FindConfigValue<bool>(document, "vr", "placeholder_steering_wheel");
config.vrObjectCulling = FindConfigValue<bool>(document, "vr", "object_culling");
config.vrHandSteering = FindConfigValue<bool>(document, "vr", "hand_steering");
config.vrCockpitItemHand = FindConfigValue<std::string>(document, "vr", "cockpit_item_hand");
@@ -1283,6 +1291,11 @@ inline bool SetVrNativeSteeringWheel(bool value) {
return WriteSetting("vr", "native_steering_wheel", value ? "true" : "false");
}
inline bool SetVrPlaceholderSteeringWheel(bool value) {
Mutable().vrPlaceholderSteeringWheel = value;
return WriteSetting("vr", "placeholder_steering_wheel", value ? "true" : "false");
}
inline bool SetVrObjectCulling(bool value) {
Mutable().vrObjectCulling = value;
return WriteSetting("vr", "object_culling", value ? "true" : "false");
@@ -1787,6 +1800,10 @@ inline bool VrNativeSteeringWheel(bool fallback = kVrNativeSteeringWheelDefault)
return Get().vrNativeSteeringWheel.value_or(fallback);
}
inline bool VrPlaceholderSteeringWheel(bool fallback = kVrPlaceholderSteeringWheelDefault) {
return Get().vrPlaceholderSteeringWheel.value_or(fallback);
}
inline bool VrObjectCulling(bool fallback = kVrObjectCullingDefault) {
return Get().vrObjectCulling.value_or(fallback);
}
+9
View File
@@ -91,6 +91,15 @@ inline float MaxWheelAngle(bool bike, const WheelTuning& tuning) noexcept {
return clamped * 0.01745329252f;
}
// Whether the overlay draws a separate VR wheel or handlebar: only when
// placeholder_steering_wheel asks for one and the vehicle's own is not the one
// turning. Without it the hands reach for the vehicle's own wherever its
// geometry is known, turning or not.
inline bool DrawsPlaceholderControl(bool steering_wheel, bool native_steering_wheel, bool native_mesh_prepared,
bool placeholder) noexcept {
return placeholder && steering_wheel && !(native_steering_wheel && native_mesh_prepared);
}
// Grips only grab. As a Wii Remote they press nothing at all (C, the game's
// look-behind, is right B); as a gamepad they are the shoulders, so a holding
// hand's squeeze is released for the game. The wheel replaces the left stick's
+14 -4
View File
@@ -168,6 +168,7 @@ int g_vrFirstPersonSeat = RuntimeConfigFile::VrFirstPersonSeat() == "custom" ? 1
float g_vrCockpitUnitsPerMeter = RuntimeConfigFile::VrCockpitUnitsPerMeter();
bool g_vrSteeringWheel = RuntimeConfigFile::VrSteeringWheel();
bool g_vrNativeSteeringWheel = RuntimeConfigFile::VrNativeSteeringWheel();
bool g_vrPlaceholderSteeringWheel = RuntimeConfigFile::VrPlaceholderSteeringWheel();
bool g_vrObjectCulling = RuntimeConfigFile::VrObjectCulling();
bool g_vrHandSteering = RuntimeConfigFile::VrHandSteering();
constexpr std::array<const char*, 3> kVrCockpitItemHands{"Left", "Right", "Off"};
@@ -1214,9 +1215,15 @@ void DrawVrSteeringWheelSettings() {
mkw::vr::MkwVRFirstPersonApplyConfiguredSettings();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Turns the wheel or handlebar of the vehicle's own model. Off draws a "
"separate VR wheel instead, which is also what appears when a vehicle's "
"own wheel cannot be animated.");
ImGui::SetTooltip("Turns the wheel or handlebar of the vehicle's own model.");
}
if (ImGui::Checkbox("Placeholder wheel or handlebar", &g_vrPlaceholderSteeringWheel)) {
RuntimeConfigFile::SetVrPlaceholderSteeringWheel(g_vrPlaceholderSteeringWheel);
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Draws a separate VR wheel (karts) or handlebar (bikes) whenever the "
"vehicle's own is not the one turning: with the option above off, or while "
"the game's draws do not take its animation, as in the race's opening pan.");
}
ImGui::EndDisabled();
if (ImGui::Checkbox("Hand steering (by heurazy)", &g_vrHandSteering)) {
@@ -1317,7 +1324,8 @@ void DrawVrSteeringWheelSettings() {
anchor.bike ? "handlebar" : "wheel",
!anchor.native_wheel.valid ? "grips not found"
: anchor.native_mesh_prepared ? "vehicle's own"
: "VR wheel",
: g_vrSteeringWheel && g_vrPlaceholderSteeringWheel ? "placeholder"
: "vehicle's own, not animated",
anchor.units_per_meter, GxNativeWheel::LastDrawCount());
}
}
@@ -1769,6 +1777,7 @@ void DrawVrCameraSettings() {
g_vrCockpitUnitsPerMeter = RuntimeConfigFile::kVrCockpitUnitsPerMeterDefault;
g_vrSteeringWheel = RuntimeConfigFile::kVrSteeringWheelDefault;
g_vrNativeSteeringWheel = RuntimeConfigFile::kVrNativeSteeringWheelDefault;
g_vrPlaceholderSteeringWheel = RuntimeConfigFile::kVrPlaceholderSteeringWheelDefault;
g_vrHandSteering = RuntimeConfigFile::kVrHandSteeringDefault;
g_vrCockpitItemHand = 0;
g_vrCockpitItemThrow = RuntimeConfigFile::kVrCockpitItemThrowDefault;
@@ -1776,6 +1785,7 @@ void DrawVrCameraSettings() {
RuntimeConfigFile::SetVrCockpitUnitsPerMeter(g_vrCockpitUnitsPerMeter);
RuntimeConfigFile::SetVrSteeringWheel(g_vrSteeringWheel);
RuntimeConfigFile::SetVrNativeSteeringWheel(g_vrNativeSteeringWheel);
RuntimeConfigFile::SetVrPlaceholderSteeringWheel(g_vrPlaceholderSteeringWheel);
RuntimeConfigFile::SetVrHandSteering(g_vrHandSteering);
RuntimeConfigFile::SetVrCockpitItemHand(RuntimeConfigFile::kVrCockpitItemHandDefault);
RuntimeConfigFile::SetVrCockpitItemThrow(g_vrCockpitItemThrow);
+9 -5
View File
@@ -1176,9 +1176,10 @@ bool ComputeCockpitAnchorLocked(const Mtx34& view_from_world, const KartPoseRead
return true;
}
// After the frame's draws: drop the wheel copies, and let the VR wheel stand
// in while no draw takes them (the race's opening pan, for one). Copies keep
// being published, so the vehicle's own wheel returns as soon as they match.
// After the frame's draws: drop the wheel copies, and let the placeholder VR
// wheel stand in (when enabled) while no draw takes them (the race's opening
// pan, for one). Copies keep being published, so the vehicle's own wheel
// returns as soon as they match.
void FinishNativeWheelFrameLocked() noexcept {
if (!g_state.wheel_arrays_posted) {
return;
@@ -1206,8 +1207,11 @@ void FinishNativeWheelFrameLocked() noexcept {
++g_state.native_wheel_switch_logs;
RT_LOG(RT_TAG_RUNTIME) << "[mkw-vr] native steering wheel: no draw took the animated copy of vehicle 0x"
<< std::hex << g_state.cockpit.body << std::dec << " in "
<< kNativeWheelUnmatchedFrames
<< " frames; drawing the VR steering wheel until one does" << std::endl;
<< kNativeWheelUnmatchedFrames << " frames; "
<< (RuntimeConfigFile::VrPlaceholderSteeringWheel()
? "drawing the placeholder VR wheel until one does"
: "the vehicle's own is drawn as the game poses it until one does")
<< std::endl;
}
}
}
+5 -4
View File
@@ -1408,6 +1408,7 @@ void OpenXRInput::UpdateDriving(XrTime display_time, const driving::SeatFrame& s
const bool hand_steering = RuntimeConfigFile::VrHandSteering();
const bool steering_wheel = RuntimeConfigFile::VrSteeringWheel();
const bool native_steering_wheel = RuntimeConfigFile::VrNativeSteeringWheel();
const bool placeholder_steering_wheel = RuntimeConfigFile::VrPlaceholderSteeringWheel();
const float dt = m_wheel_time != 0 && display_time > m_wheel_time
? static_cast<float>(display_time - m_wheel_time) * 1.0e-9f
: 1.0f / 90.0f;
@@ -1417,10 +1418,10 @@ void OpenXRInput::UpdateDriving(XrTime display_time, const driving::SeatFrame& s
snapshot.cockpit_active = true;
snapshot.hand_steering = hand_steering;
snapshot.bike = anchor.bike;
// The vehicle's own control is the one turning (or none is shown at all),
// so the overlay adds no separate wheel.
snapshot.synthetic_control =
steering_wheel && !(native_steering_wheel && anchor.native_mesh_prepared);
// A separate wheel only when asked for and the vehicle's own is not the one
// turning.
snapshot.synthetic_control = driving::DrawsPlaceholderControl(
steering_wheel, native_steering_wheel, anchor.native_mesh_prepared, placeholder_steering_wheel);
// Which control the hands reach for: the vehicle's own wherever its
// geometry is known and no separate wheel is drawn, a handlebar always
+5
View File
@@ -26,6 +26,11 @@ int main() {
Require(Parse("[vr]\ncockpit_item_throw = false\n").vrCockpitItemThrow == false);
Require(Parse("[vr]\ncockpit_item_throw = true\n").vrCockpitItemThrow == true);
Require(!Parse("[vr]\n").vrCockpitItemThrow.has_value());
// [vr] placeholder_steering_wheel: the separate VR wheel or handlebar is opt-in.
Require(!RuntimeConfigFile::kVrPlaceholderSteeringWheelDefault);
Require(Parse("[vr]\nplaceholder_steering_wheel = true\n").vrPlaceholderSteeringWheel == true);
Require(Parse("[vr]\nplaceholder_steering_wheel = false\n").vrPlaceholderSteeringWheel == false);
Require(!Parse("[vr]\n").vrPlaceholderSteeringWheel.has_value());
// [vr] foveation: the Quest's foveated rendering level, index-matched to
// aurora_set_stereo_foveation.
for (std::string_view level : RuntimeConfigFile::kVrFoveationLevels) {
+16
View File
@@ -108,6 +108,21 @@ void TestSeatFrame() {
CheckNear(m[11], -1.0f, "lean turns up into forward", 1e-5f);
}
void TestPlaceholderControl() {
// Arguments: steering_wheel, native_steering_wheel, native_mesh_prepared, placeholder.
Check(!driving::DrawsPlaceholderControl(true, true, false, false),
"no placeholder while the vehicle's own is not animated, unless asked for");
Check(!driving::DrawsPlaceholderControl(true, false, false, false),
"no placeholder with the vehicle's own wheel off, unless asked for");
Check(driving::DrawsPlaceholderControl(true, true, false, true),
"the placeholder stands in while the vehicle's own is not animated");
Check(driving::DrawsPlaceholderControl(true, false, true, true),
"the placeholder replaces the vehicle's own wheel when that is off");
Check(!driving::DrawsPlaceholderControl(true, true, true, true),
"no placeholder while the vehicle's own is the one turning");
Check(!driving::DrawsPlaceholderControl(false, false, false, true), "no wheel turns at all");
}
} // namespace
int main() {
@@ -115,6 +130,7 @@ int main() {
TestMaxAngle();
TestVisual();
TestSeatFrame();
TestPlaceholderControl();
if (g_failures != 0) {
std::cerr << g_failures << " check(s) failed\n";
return 1;