mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 06:00:25 +02:00
Added "Lean back angle" option
This commit is contained in:
1 parent
6e7e0f075b
commit
913eda2b3a
4 files changed
+99
-5
No files matched your search
@@ -63,6 +63,7 @@ struct RuntimeUserConfig {
|
|||||||
std::optional<bool> vrFirstPersonHideDriver;
|
std::optional<bool> vrFirstPersonHideDriver;
|
||||||
std::optional<int32_t> vrFirstPersonHiddenModel;
|
std::optional<int32_t> vrFirstPersonHiddenModel;
|
||||||
std::optional<std::string> vrRecenterKey;
|
std::optional<std::string> vrRecenterKey;
|
||||||
|
std::optional<float> vrLeanBackDegrees;
|
||||||
std::optional<float> audioVolume;
|
std::optional<float> audioVolume;
|
||||||
std::optional<float> audioMusicVolume;
|
std::optional<float> audioMusicVolume;
|
||||||
std::optional<float> audioSoundEffectsVolume;
|
std::optional<float> audioSoundEffectsVolume;
|
||||||
@@ -150,6 +151,10 @@ inline constexpr float kVrFirstPersonHeadOffsetLimit = 10.0f;
|
|||||||
// SDL scancode name, spelled the way SDL_GetScancodeName produces it. An
|
// SDL scancode name, spelled the way SDL_GetScancodeName produces it. An
|
||||||
// empty string leaves the recenter hotkey unbound, menu button only.
|
// empty string leaves the recenter hotkey unbound, menu button only.
|
||||||
inline constexpr std::string_view kVrRecenterKeyDefault = "F9";
|
inline constexpr std::string_view kVrRecenterKeyDefault = "F9";
|
||||||
|
// Fixed pitch of the game camera for a player sitting reclined, in degrees.
|
||||||
|
// Positive leans the camera back with you; 0 disables it entirely.
|
||||||
|
inline constexpr float kVrLeanBackDegreesDefault = 0.0f;
|
||||||
|
inline constexpr float kVrLeanBackDegreesLimit = 45.0f;
|
||||||
|
|
||||||
inline std::string Trim(std::string_view text) {
|
inline std::string Trim(std::string_view text) {
|
||||||
size_t begin = 0;
|
size_t begin = 0;
|
||||||
@@ -387,7 +392,12 @@ inline void EnsureConfigFile() {
|
|||||||
"# the game. Applies during an immersive race; the menu screen is\n"
|
"# the game. Applies during an immersive race; the menu screen is\n"
|
||||||
"# head-locked already. Rebindable from the F10 menu under VR. Leave\n"
|
"# head-locked already. Rebindable from the F10 menu under VR. Leave\n"
|
||||||
"# empty to unbind.\n"
|
"# empty to unbind.\n"
|
||||||
"recenter_key = \"F9\"\n\n"
|
"recenter_key = \"F9\"\n"
|
||||||
|
"# Fixed pitch of the game camera, in degrees, for a player sitting\n"
|
||||||
|
"# reclined. Positive tilts the camera back with you, so 20 here cancels\n"
|
||||||
|
"# reclining about 20 degrees and puts the track back in front of you.\n"
|
||||||
|
"# 0 disables it. Changeable live from the F10 menu under VR.\n"
|
||||||
|
"lean_back_degrees = 0.0\n\n"
|
||||||
"[audio]\n"
|
"[audio]\n"
|
||||||
"volume = 1.0\n"
|
"volume = 1.0\n"
|
||||||
"music_volume = 1.0\n"
|
"music_volume = 1.0\n"
|
||||||
@@ -573,6 +583,10 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
|
|||||||
if (auto value = FindConfigValue<std::string>(document, "vr", "recenter_key")) {
|
if (auto value = FindConfigValue<std::string>(document, "vr", "recenter_key")) {
|
||||||
config.vrRecenterKey = *value;
|
config.vrRecenterKey = *value;
|
||||||
}
|
}
|
||||||
|
if (auto value = FindConfigFloat(document, "vr", "lean_back_degrees");
|
||||||
|
value && *value >= -kVrLeanBackDegreesLimit && *value <= kVrLeanBackDegreesLimit) {
|
||||||
|
config.vrLeanBackDegrees = static_cast<float>(*value);
|
||||||
|
}
|
||||||
if (auto value = FindConfigInt(document, "vr", "first_person_hidden_model");
|
if (auto value = FindConfigInt(document, "vr", "first_person_hidden_model");
|
||||||
value && *value >= -1 && *value <= 31) {
|
value && *value >= -1 && *value <= 31) {
|
||||||
config.vrFirstPersonHiddenModel = static_cast<int32_t>(*value);
|
config.vrFirstPersonHiddenModel = static_cast<int32_t>(*value);
|
||||||
@@ -853,6 +867,14 @@ inline bool SetVrRecenterKey(std::string value) {
|
|||||||
return WriteSetting("vr", "recenter_key", FormatString(value));
|
return WriteSetting("vr", "recenter_key", FormatString(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool SetVrLeanBackDegrees(float value) {
|
||||||
|
value = std::clamp(value, -kVrLeanBackDegreesLimit, kVrLeanBackDegreesLimit);
|
||||||
|
Mutable().vrLeanBackDegrees = value;
|
||||||
|
std::ostringstream formatted;
|
||||||
|
formatted << value;
|
||||||
|
return WriteSetting("vr", "lean_back_degrees", formatted.str());
|
||||||
|
}
|
||||||
|
|
||||||
inline bool SetVrFirstPersonHideDriver(bool value) {
|
inline bool SetVrFirstPersonHideDriver(bool value) {
|
||||||
Mutable().vrFirstPersonHideDriver = value;
|
Mutable().vrFirstPersonHideDriver = value;
|
||||||
return WriteSetting("vr", "first_person_hide_driver", value ? "true" : "false");
|
return WriteSetting("vr", "first_person_hide_driver", value ? "true" : "false");
|
||||||
@@ -1149,6 +1171,11 @@ inline std::string VrRecenterKey(std::string fallback = std::string(kVrRecenterK
|
|||||||
return Get().vrRecenterKey.value_or(std::move(fallback));
|
return Get().vrRecenterKey.value_or(std::move(fallback));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline float VrLeanBackDegrees(float fallback = kVrLeanBackDegreesDefault) {
|
||||||
|
return std::clamp(Get().vrLeanBackDegrees.value_or(fallback),
|
||||||
|
-kVrLeanBackDegreesLimit, kVrLeanBackDegreesLimit);
|
||||||
|
}
|
||||||
|
|
||||||
inline bool VrFirstPersonHideDriver(bool fallback = kVrFirstPersonHideDriverDefault) {
|
inline bool VrFirstPersonHideDriver(bool fallback = kVrFirstPersonHideDriverDefault) {
|
||||||
return Get().vrFirstPersonHideDriver.value_or(fallback);
|
return Get().vrFirstPersonHideDriver.value_or(fallback);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,4 +44,12 @@ std::string OpenXRLastError();
|
|||||||
// immersive race, where the head-locked menu screen has no origin to recenter.
|
// immersive race, where the head-locked menu screen has no origin to recenter.
|
||||||
void OpenXRRequestRecenter() noexcept;
|
void OpenXRRequestRecenter() noexcept;
|
||||||
|
|
||||||
|
// Sets a fixed pitch of the game camera for a player sitting reclined, in
|
||||||
|
// degrees, clamped to RuntimeConfigFile::kVrLeanBackDegreesLimit. Positive
|
||||||
|
// tilts the camera back with the player, so an angle matching how far they are
|
||||||
|
// reclined puts the track back in front of them. 0 applies no pitch at all.
|
||||||
|
// Applies to the immersive race view only; callable from any thread and read
|
||||||
|
// once per published frame.
|
||||||
|
void OpenXRSetLeanBackDegrees(float degrees) noexcept;
|
||||||
|
|
||||||
} // namespace mkw::vr
|
} // namespace mkw::vr
|
||||||
@@ -121,6 +121,7 @@ SDL_Scancode g_vrRecenterScancode = [] {
|
|||||||
: SDL_GetScancodeFromName(name.c_str());
|
: SDL_GetScancodeFromName(name.c_str());
|
||||||
}();
|
}();
|
||||||
bool g_vrRecenterRebinding = false;
|
bool g_vrRecenterRebinding = false;
|
||||||
|
float g_vrLeanBackDegrees = RuntimeConfigFile::VrLeanBackDegrees();
|
||||||
uint32_t g_disabledPostProcessingPaths = RuntimeConfigFile::DisabledPostProcessingPaths(0);
|
uint32_t g_disabledPostProcessingPaths = RuntimeConfigFile::DisabledPostProcessingPaths(0);
|
||||||
std::array<int32_t, PAD_MAX_CONTROLLERS> g_configuredControllerIndices = [] {
|
std::array<int32_t, PAD_MAX_CONTROLLERS> g_configuredControllerIndices = [] {
|
||||||
std::array<int32_t, PAD_MAX_CONTROLLERS> indices{};
|
std::array<int32_t, PAD_MAX_CONTROLLERS> indices{};
|
||||||
@@ -909,6 +910,20 @@ void DrawVrSettings() {
|
|||||||
ImGui::SetTooltip("Click to rebind. Esc cancels, Backspace unbinds.");
|
ImGui::SetTooltip("Click to rebind. Esc cancels, Backspace unbinds.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ImGui::SliderFloat("Lean back angle (deg)", &g_vrLeanBackDegrees,
|
||||||
|
-RuntimeConfigFile::kVrLeanBackDegreesLimit,
|
||||||
|
RuntimeConfigFile::kVrLeanBackDegreesLimit, "%.1f")) {
|
||||||
|
mkw::vr::OpenXRSetLeanBackDegrees(g_vrLeanBackDegrees);
|
||||||
|
RuntimeConfigFile::SetVrLeanBackDegrees(g_vrLeanBackDegrees);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::SetTooltip(
|
||||||
|
"Tilts the game camera back with you when you play reclined, so set it to "
|
||||||
|
"roughly how far back your seat is and the track comes back in front of you "
|
||||||
|
"instead of above. 0 applies no tilt. Unlike recentering, this deliberately "
|
||||||
|
"does pitch the view, and looking sideways while it is set will roll the "
|
||||||
|
"horizon the way a real recline would.");
|
||||||
|
}
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("VR camera");
|
ImGui::Text("VR camera");
|
||||||
if (ImGui::Checkbox("First-person camera", &g_vrFirstPerson)) {
|
if (ImGui::Checkbox("First-person camera", &g_vrFirstPerson)) {
|
||||||
|
|||||||
@@ -37,6 +37,8 @@
|
|||||||
namespace mkw::vr {
|
namespace mkw::vr {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
inline constexpr float kDegreesToRadians = 0.01745329252f;
|
||||||
|
|
||||||
void ConfigurePolicy(bool enabled) noexcept {
|
void ConfigurePolicy(bool enabled) noexcept {
|
||||||
MkwVRPolicyReset();
|
MkwVRPolicyReset();
|
||||||
MkwVRPolicyConfig config{};
|
MkwVRPolicyConfig config{};
|
||||||
@@ -78,6 +80,15 @@ Quaternion Conjugate(Quaternion value) noexcept {
|
|||||||
return {-value.x, -value.y, -value.z, value.w};
|
return {-value.x, -value.y, -value.z, value.w};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Quaternion Multiply(const Quaternion& left, const Quaternion& right) noexcept {
|
||||||
|
return Normalize({
|
||||||
|
left.w * right.x + left.x * right.w + left.y * right.z - left.z * right.y,
|
||||||
|
left.w * right.y - left.x * right.z + left.y * right.w + left.z * right.x,
|
||||||
|
left.w * right.z + left.x * right.y - left.y * right.x + left.z * right.w,
|
||||||
|
left.w * right.w - left.x * right.x - left.y * right.y - left.z * right.z,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
std::array<float, 3> Rotate(const Quaternion& q, const std::array<float, 3>& value) noexcept {
|
std::array<float, 3> Rotate(const Quaternion& q, const std::array<float, 3>& value) noexcept {
|
||||||
// Expanded q * [v,0] * conjugate(q), avoiding two temporary normalizations.
|
// Expanded q * [v,0] * conjugate(q), avoiding two temporary normalizations.
|
||||||
const float tx = 2.0f * (q.y * value[2] - q.z * value[1]);
|
const float tx = 2.0f * (q.y * value[2] - q.z * value[1]);
|
||||||
@@ -155,13 +166,26 @@ void ProjectionFromFov(const XrFovf& fov, float output[16]) noexcept {
|
|||||||
// leaves the game's horizon level and its forward fixed to the reference space.
|
// leaves the game's horizon level and its forward fixed to the reference space.
|
||||||
// Composing a latched head orientation in here instead would bake that instant's
|
// Composing a latched head orientation in here instead would bake that instant's
|
||||||
// pitch and roll into the neutral and tilt the horizon for the rest of the session.
|
// pitch and roll into the neutral and tilt the horizon for the rest of the session.
|
||||||
|
//
|
||||||
|
// lean_back_radians is the one deliberate exception: a fixed pitch of the game
|
||||||
|
// camera about the reference space's right axis, for a player sitting reclined.
|
||||||
|
// It multiplies in on the right, so it turns the world before the head rotation
|
||||||
|
// rather than after it, which is what makes it cancel a reclined head exactly
|
||||||
|
// and, when you then look sideways, roll the view the way a real recline would.
|
||||||
void ViewFromBase(const XrPosef& eye_pose, const std::array<float, 3>& base_position,
|
void ViewFromBase(const XrPosef& eye_pose, const std::array<float, 3>& base_position,
|
||||||
bool position_valid, float units_per_meter, float output[12]) noexcept {
|
bool position_valid, float units_per_meter, float lean_back_radians,
|
||||||
|
float output[12]) noexcept {
|
||||||
const Quaternion eye = Normalize({eye_pose.orientation.x, eye_pose.orientation.y,
|
const Quaternion eye = Normalize({eye_pose.orientation.x, eye_pose.orientation.y,
|
||||||
eye_pose.orientation.z, eye_pose.orientation.w});
|
eye_pose.orientation.z, eye_pose.orientation.w});
|
||||||
const Quaternion inverse_eye = Conjugate(eye);
|
const Quaternion inverse_eye = Conjugate(eye);
|
||||||
float rotation[9];
|
float rotation[9];
|
||||||
RotationMatrix(inverse_eye, rotation);
|
if (lean_back_radians == 0.0f) {
|
||||||
|
RotationMatrix(inverse_eye, rotation);
|
||||||
|
} else {
|
||||||
|
const float half_angle = 0.5f * lean_back_radians;
|
||||||
|
const Quaternion lean{std::sin(half_angle), 0.0f, 0.0f, std::cos(half_angle)};
|
||||||
|
RotationMatrix(Multiply(inverse_eye, lean), rotation);
|
||||||
|
}
|
||||||
|
|
||||||
std::array<float, 3> translation{};
|
std::array<float, 3> translation{};
|
||||||
if (position_valid) {
|
if (position_valid) {
|
||||||
@@ -326,6 +350,13 @@ public:
|
|||||||
recenter_requested_.store(true, std::memory_order_release);
|
recenter_requested_.store(true, std::memory_order_release);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SetLeanBackDegrees(float degrees) noexcept {
|
||||||
|
lean_back_degrees_.store(
|
||||||
|
std::clamp(degrees, -RuntimeConfigFile::kVrLeanBackDegreesLimit,
|
||||||
|
RuntimeConfigFile::kVrLeanBackDegreesLimit),
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
void ServiceProducerFrameBoundary() noexcept {
|
void ServiceProducerFrameBoundary() noexcept {
|
||||||
if (teardown_requested_.load(std::memory_order_acquire)) {
|
if (teardown_requested_.load(std::memory_order_acquire)) {
|
||||||
Shutdown();
|
Shutdown();
|
||||||
@@ -596,12 +627,16 @@ private:
|
|||||||
base_position_valid_ = true;
|
base_position_valid_ = true;
|
||||||
}
|
}
|
||||||
last_immersive_ = true;
|
last_immersive_ = true;
|
||||||
|
// Read once so both eyes are built from the same angle even if the
|
||||||
|
// settings slider moves between them.
|
||||||
|
const float lean_back_radians =
|
||||||
|
lean_back_degrees_.load(std::memory_order_relaxed) * kDegreesToRadians;
|
||||||
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||||
ProjectionFromFov(source.xr_frame.views[eye].fov,
|
ProjectionFromFov(source.xr_frame.views[eye].fov,
|
||||||
destination.eyes[eye].projection);
|
destination.eyes[eye].projection);
|
||||||
ViewFromBase(source.xr_frame.views[eye].pose, base_position_,
|
ViewFromBase(source.xr_frame.views[eye].pose, base_position_,
|
||||||
position_valid && base_position_valid_,
|
position_valid && base_position_valid_, units_per_meter,
|
||||||
units_per_meter, destination.eyes[eye].viewFromCenter);
|
lean_back_radians, destination.eyes[eye].viewFromCenter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -644,6 +679,7 @@ private:
|
|||||||
std::atomic_bool running_{false};
|
std::atomic_bool running_{false};
|
||||||
std::atomic_bool teardown_requested_{false};
|
std::atomic_bool teardown_requested_{false};
|
||||||
std::atomic_bool recenter_requested_{false};
|
std::atomic_bool recenter_requested_{false};
|
||||||
|
std::atomic<float> lean_back_degrees_{RuntimeConfigFile::VrLeanBackDegrees()};
|
||||||
std::atomic<PublishedFrame*> published_{nullptr};
|
std::atomic<PublishedFrame*> published_{nullptr};
|
||||||
PublishedFrame published_frame_{};
|
PublishedFrame published_frame_{};
|
||||||
std::mutex published_mutex_;
|
std::mutex published_mutex_;
|
||||||
@@ -722,6 +758,14 @@ void OpenXRRequestRecenter() noexcept {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void OpenXRSetLeanBackDegrees(float degrees) noexcept {
|
||||||
|
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||||
|
OpenXRIntegration::Get().SetLeanBackDegrees(degrees);
|
||||||
|
#else
|
||||||
|
(void)degrees;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
std::string OpenXRLastError() {
|
std::string OpenXRLastError() {
|
||||||
#if !defined(MKW_ENABLE_OPENXR)
|
#if !defined(MKW_ENABLE_OPENXR)
|
||||||
return "this build was compiled without OpenXR support";
|
return "this build was compiled without OpenXR support";
|
||||||
|
|||||||
Reference in new issue
Block a user