mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 08:00:25 +02:00
Added hotkey/menu recenter & Removed head orientation into the view rotation
This commit is contained in:
1 parent
bf8539999d
commit
6e7e0f075b
4 files changed
+150
-54
No files matched your search
@@ -7,6 +7,7 @@
|
||||
#include "runtime_log.h"
|
||||
#include "vr/mkw_vr_first_person.h"
|
||||
#include "vr/mkw_vr_policy.h"
|
||||
#include "vr/openxr_integration.h"
|
||||
#include "wii_remote_input.h"
|
||||
|
||||
#include <imgui.h>
|
||||
@@ -112,6 +113,14 @@ float g_vrFirstPersonHeadForward = RuntimeConfigFile::VrFirstPersonHeadForwardMe
|
||||
float g_vrFirstPersonHeadRight = RuntimeConfigFile::VrFirstPersonHeadRightMeters();
|
||||
bool g_vrFirstPersonHideDriver = RuntimeConfigFile::VrFirstPersonHideDriver();
|
||||
int g_vrFirstPersonHiddenModel = RuntimeConfigFile::VrFirstPersonHiddenModel();
|
||||
// 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 = [] {
|
||||
const std::string name = RuntimeConfigFile::VrRecenterKey();
|
||||
return name.empty() ? SDL_SCANCODE_UNKNOWN
|
||||
: SDL_GetScancodeFromName(name.c_str());
|
||||
}();
|
||||
bool g_vrRecenterRebinding = false;
|
||||
uint32_t g_disabledPostProcessingPaths = RuntimeConfigFile::DisabledPostProcessingPaths(0);
|
||||
std::array<int32_t, PAD_MAX_CONTROLLERS> g_configuredControllerIndices = [] {
|
||||
std::array<int32_t, PAD_MAX_CONTROLLERS> indices{};
|
||||
@@ -869,6 +878,38 @@ void DrawVrSettings() {
|
||||
"hud_distance_meters read at launch.");
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::Text("VR view");
|
||||
if (ImGui::Button("Recenter view")) {
|
||||
mkw::vr::OpenXRRequestRecenter();
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip(
|
||||
"Makes where you are sitting right now the centre of the view. Position "
|
||||
"only, so the horizon stays level and forward is unchanged; use your "
|
||||
"headset's own recenter to change forward. Applies during an immersive "
|
||||
"race, as the menu screen already follows your head.");
|
||||
}
|
||||
ImGui::SameLine();
|
||||
// Click to arm, then the next key press is captured in HandleEvents.
|
||||
if (g_vrRecenterRebinding) {
|
||||
if (ImGui::Button("Press a key... (Esc to cancel)###VrRecenterKey")) {
|
||||
g_vrRecenterRebinding = false;
|
||||
}
|
||||
} else {
|
||||
const char* name = g_vrRecenterScancode == SDL_SCANCODE_UNKNOWN
|
||||
? nullptr
|
||||
: SDL_GetScancodeName(g_vrRecenterScancode);
|
||||
const std::string label =
|
||||
std::string("Hotkey: ") + ((name && *name) ? name : "unbound") +
|
||||
"###VrRecenterKey";
|
||||
if (ImGui::Button(label.c_str())) {
|
||||
g_vrRecenterRebinding = true;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::SetTooltip("Click to rebind. Esc cancels, Backspace unbinds.");
|
||||
}
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::Text("VR camera");
|
||||
if (ImGui::Checkbox("First-person camera", &g_vrFirstPerson)) {
|
||||
RuntimeConfigFile::SetVrFirstPerson(g_vrFirstPerson);
|
||||
@@ -1119,6 +1160,27 @@ bool IsToggleKey(const SDL_Event& event, SDL_Scancode code) {
|
||||
return event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat && event.key.scancode == code;
|
||||
}
|
||||
|
||||
// Consumes the next key press into the recenter binding while the menu
|
||||
// button is armed. Esc leaves the existing binding alone, Backspace clears
|
||||
// it; anything else becomes the new hotkey.
|
||||
bool CaptureVrRecenterBinding(const SDL_Event& event) {
|
||||
if (!g_vrRecenterRebinding || event.type != SDL_EVENT_KEY_DOWN || event.key.repeat) {
|
||||
return false;
|
||||
}
|
||||
g_vrRecenterRebinding = false;
|
||||
if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
|
||||
return true;
|
||||
}
|
||||
g_vrRecenterScancode = event.key.scancode == SDL_SCANCODE_BACKSPACE
|
||||
? SDL_SCANCODE_UNKNOWN
|
||||
: event.key.scancode;
|
||||
const char* name = g_vrRecenterScancode == SDL_SCANCODE_UNKNOWN
|
||||
? nullptr
|
||||
: SDL_GetScancodeName(g_vrRecenterScancode);
|
||||
RuntimeConfigFile::SetVrRecenterKey(name ? name : "");
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsMouseActivity(const SDL_Event& event) {
|
||||
switch (event.type) {
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
@@ -1201,9 +1263,16 @@ void HandleEvents(const AuroraEvent* events) noexcept {
|
||||
continue;
|
||||
}
|
||||
controller_mapping_wizard::HandleSdlEvent(ev->sdl);
|
||||
if (CaptureVrRecenterBinding(ev->sdl)) {
|
||||
continue;
|
||||
}
|
||||
if (IsToggleKey(ev->sdl, SDL_SCANCODE_F10)) {
|
||||
SetTopBarVisible(!g_topBarVisible);
|
||||
}
|
||||
if (g_vrRecenterScancode != SDL_SCANCODE_UNKNOWN &&
|
||||
IsToggleKey(ev->sdl, g_vrRecenterScancode)) {
|
||||
mkw::vr::OpenXRRequestRecenter();
|
||||
}
|
||||
if (IsMouseActivity(ev->sdl)) {
|
||||
g_lastMouseActivity = Clock::now();
|
||||
}
|
||||
|
||||
@@ -60,11 +60,6 @@ struct Quaternion {
|
||||
float w = 1.0f;
|
||||
};
|
||||
|
||||
struct Pose {
|
||||
Quaternion orientation{};
|
||||
std::array<float, 3> position{};
|
||||
};
|
||||
|
||||
Quaternion Normalize(Quaternion value) noexcept {
|
||||
const float length_squared = value.x * value.x + value.y * value.y +
|
||||
value.z * value.z + value.w * value.w;
|
||||
@@ -83,15 +78,6 @@ Quaternion Conjugate(Quaternion value) noexcept {
|
||||
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 {
|
||||
// Expanded q * [v,0] * conjugate(q), avoiding two temporary normalizations.
|
||||
const float tx = 2.0f * (q.y * value[2] - q.z * value[1]);
|
||||
@@ -126,29 +112,16 @@ void RotationMatrix(const Quaternion& value, float matrix[9]) noexcept {
|
||||
matrix[8] = 1.0f - 2.0f * (xx + yy);
|
||||
}
|
||||
|
||||
Pose CenterPose(const OpenXRFrame& frame, bool position_valid) noexcept {
|
||||
// Midpoint between the eyes: the head position the tracking origin is latched
|
||||
// to. Callers check XR_VIEW_STATE_POSITION_VALID_BIT first.
|
||||
std::array<float, 3> CenterPosition(const OpenXRFrame& frame) noexcept {
|
||||
const auto& left = frame.views[0].pose;
|
||||
const auto& right = frame.views[1].pose;
|
||||
Quaternion l{left.orientation.x, left.orientation.y, left.orientation.z,
|
||||
left.orientation.w};
|
||||
Quaternion r{right.orientation.x, right.orientation.y, right.orientation.z,
|
||||
right.orientation.w};
|
||||
l = Normalize(l);
|
||||
r = Normalize(r);
|
||||
const float dot = l.x * r.x + l.y * r.y + l.z * r.z + l.w * r.w;
|
||||
if (dot < 0.0f) {
|
||||
r = {-r.x, -r.y, -r.z, -r.w};
|
||||
}
|
||||
Pose center;
|
||||
center.orientation = Normalize({l.x + r.x, l.y + r.y, l.z + r.z, l.w + r.w});
|
||||
if (position_valid) {
|
||||
center.position = {
|
||||
(left.position.x + right.position.x) * 0.5f,
|
||||
(left.position.y + right.position.y) * 0.5f,
|
||||
(left.position.z + right.position.z) * 0.5f,
|
||||
};
|
||||
}
|
||||
return center;
|
||||
return {
|
||||
(left.position.x + right.position.x) * 0.5f,
|
||||
(left.position.y + right.position.y) * 0.5f,
|
||||
(left.position.z + right.position.z) * 0.5f,
|
||||
};
|
||||
}
|
||||
|
||||
void IdentityEye(AuroraStereoEye& eye) noexcept {
|
||||
@@ -177,21 +150,25 @@ void ProjectionFromFov(const XrFovf& fov, float output[16]) noexcept {
|
||||
output[6] = (up + down) * inverse_height;
|
||||
}
|
||||
|
||||
void ViewFromBase(const XrPosef& eye_pose, const Pose& base, bool position_valid,
|
||||
float units_per_meter, float output[12]) noexcept {
|
||||
// The base is a position and nothing else. OpenXR keeps its reference spaces
|
||||
// gravity-aligned, so handing the headset's rotation to the game camera as-is
|
||||
// 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
|
||||
// pitch and roll into the neutral and tilt the horizon for the rest of the session.
|
||||
void ViewFromBase(const XrPosef& eye_pose, const std::array<float, 3>& base_position,
|
||||
bool position_valid, float units_per_meter, float output[12]) noexcept {
|
||||
const Quaternion eye = Normalize({eye_pose.orientation.x, eye_pose.orientation.y,
|
||||
eye_pose.orientation.z, eye_pose.orientation.w});
|
||||
const Quaternion inverse_eye = Conjugate(eye);
|
||||
const Quaternion delta = Multiply(inverse_eye, base.orientation);
|
||||
float rotation[9];
|
||||
RotationMatrix(delta, rotation);
|
||||
RotationMatrix(inverse_eye, rotation);
|
||||
|
||||
std::array<float, 3> translation{};
|
||||
if (position_valid) {
|
||||
const std::array<float, 3> base_to_eye{
|
||||
base.position[0] - eye_pose.position.x,
|
||||
base.position[1] - eye_pose.position.y,
|
||||
base.position[2] - eye_pose.position.z,
|
||||
base_position[0] - eye_pose.position.x,
|
||||
base_position[1] - eye_pose.position.y,
|
||||
base_position[2] - eye_pose.position.z,
|
||||
};
|
||||
translation = Rotate(inverse_eye, base_to_eye);
|
||||
}
|
||||
@@ -345,6 +322,10 @@ public:
|
||||
|
||||
bool IsRunning() const noexcept { return running_.load(std::memory_order_acquire); }
|
||||
|
||||
void RequestRecenter() noexcept {
|
||||
recenter_requested_.store(true, std::memory_order_release);
|
||||
}
|
||||
|
||||
void ServiceProducerFrameBoundary() noexcept {
|
||||
if (teardown_requested_.load(std::memory_order_acquire)) {
|
||||
Shutdown();
|
||||
@@ -591,25 +572,34 @@ private:
|
||||
IdentityEye(destination.eyes[eye]);
|
||||
}
|
||||
if (!immersive) {
|
||||
// The virtual screen is submitted in view space, so it is
|
||||
// head-locked and has no origin to recenter. Drop the request rather
|
||||
// than leaving it queued for a race start that latches the position
|
||||
// on its own anyway.
|
||||
recenter_requested_.store(false, std::memory_order_relaxed);
|
||||
last_immersive_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const bool position_valid =
|
||||
(source.xr_frame.view_state_flags & XR_VIEW_STATE_POSITION_VALID_BIT) != 0;
|
||||
if (!base_pose_valid_ || !last_immersive_) {
|
||||
base_pose_ = CenterPose(source.xr_frame, position_valid);
|
||||
base_pose_valid_ = true;
|
||||
base_position_valid_ = position_valid;
|
||||
} else if (position_valid && !base_position_valid_) {
|
||||
base_pose_.position = CenterPose(source.xr_frame, true).position;
|
||||
if (recenter_requested_.exchange(false, std::memory_order_acq_rel)) {
|
||||
ResetTrackingOrigin();
|
||||
}
|
||||
// Latch on the first immersive frame, after a recenter or an origin
|
||||
// change, and on re-entry from the virtual screen so a race start
|
||||
// recenters a player who shifted during the menus. Position only: the
|
||||
// heading and the horizon belong to the reference space, so no
|
||||
// transition here can tilt the view or redefine forward.
|
||||
if (position_valid && (!base_position_valid_ || !last_immersive_)) {
|
||||
base_position_ = CenterPosition(source.xr_frame);
|
||||
base_position_valid_ = true;
|
||||
}
|
||||
last_immersive_ = true;
|
||||
for (uint32_t eye = 0; eye < kOpenXREyeCount; ++eye) {
|
||||
ProjectionFromFov(source.xr_frame.views[eye].fov,
|
||||
destination.eyes[eye].projection);
|
||||
ViewFromBase(source.xr_frame.views[eye].pose, base_pose_,
|
||||
ViewFromBase(source.xr_frame.views[eye].pose, base_position_,
|
||||
position_valid && base_position_valid_,
|
||||
units_per_meter, destination.eyes[eye].viewFromCenter);
|
||||
}
|
||||
@@ -622,8 +612,7 @@ private:
|
||||
}
|
||||
|
||||
void ResetTrackingOrigin() noexcept {
|
||||
base_pose_ = {};
|
||||
base_pose_valid_ = false;
|
||||
base_position_ = {};
|
||||
base_position_valid_ = false;
|
||||
last_immersive_ = false;
|
||||
}
|
||||
@@ -654,6 +643,7 @@ private:
|
||||
std::atomic_bool stop_{false};
|
||||
std::atomic_bool running_{false};
|
||||
std::atomic_bool teardown_requested_{false};
|
||||
std::atomic_bool recenter_requested_{false};
|
||||
std::atomic<PublishedFrame*> published_{nullptr};
|
||||
PublishedFrame published_frame_{};
|
||||
std::mutex published_mutex_;
|
||||
@@ -661,8 +651,7 @@ private:
|
||||
std::condition_variable stop_cv_;
|
||||
mutable std::mutex error_mutex_;
|
||||
std::string last_error_;
|
||||
Pose base_pose_{};
|
||||
bool base_pose_valid_ = false;
|
||||
std::array<float, 3> base_position_{};
|
||||
bool base_position_valid_ = false;
|
||||
bool last_immersive_ = false;
|
||||
uint64_t applied_session_run_serial_ = 0;
|
||||
@@ -727,6 +716,12 @@ bool OpenXRIsRunning() noexcept {
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenXRRequestRecenter() noexcept {
|
||||
#if defined(MKW_ENABLE_OPENXR) && defined(_WIN32)
|
||||
OpenXRIntegration::Get().RequestRecenter();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string OpenXRLastError() {
|
||||
#if !defined(MKW_ENABLE_OPENXR)
|
||||
return "this build was compiled without OpenXR support";
|
||||
|
||||
Reference in new issue
Block a user