Implement OpenXR Settings Panel and Stereo Overlay

This commit is contained in:
iChris4 committed 2026-09-17 04:32:22 +02:00
1 parent f222eedf6b
commit 1346e53cd6
22 files changed
+1466 -101

No files matched your search

+190
View File
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-3.0-or-later
//
// The in-headset settings panel's controller handling, tested without a
// headset: the thumbstick chord that opens and closes it, the release latch that
// keeps a closing press out of the game, selection, scrolling, and where a hit
// lands on the panel's canvas.
#include "vr/openxr_settings_panel.h"
#include <cmath>
#include <iostream>
namespace {
using namespace mkw::vr;
using namespace mkw::vr::settings_panel;
int g_failures = 0;
void Check(bool condition, const char* what) {
if (!condition) {
++g_failures;
std::cerr << "FAILED: " << what << '\n';
}
}
void CheckNear(float actual, float expected, const char* what, float tolerance = 1.0e-3f) {
if (!(std::fabs(actual - expected) <= tolerance)) {
++g_failures;
std::cerr << "FAILED: " << what << " (expected " << expected << ", got " << actual << ")\n";
}
}
constexpr float kDt = 1.0f / 90.0f;
std::array<HandInputs, 2> Released() {
return {};
}
std::array<HandInputs, 2> Chord() {
std::array<HandInputs, 2> hands{};
hands[0].thumbstick_click = true;
hands[1].thumbstick_click = true;
return hands;
}
void ChordOpensAndClosesOnce() {
Controls controls;
bool open = false;
auto one = Released();
one[0].thumbstick_click = true;
Frame frame = controls.Update(one, open, kDt);
Check(!open && !frame.withheld, "one thumbstick click alone neither opens nor withholds");
frame = controls.Update(Chord(), open, kDt);
Check(open && frame.open && frame.withheld, "clicking both thumbsticks opens the panel");
frame = controls.Update(Chord(), open, kDt);
Check(open, "holding the chord does not toggle again");
frame = controls.Update(Released(), open, kDt);
Check(open && frame.withheld, "the panel stays open and keeps the controllers after the chord is released");
frame = controls.Update(Chord(), open, kDt);
Check(!open && !frame.open, "the chord closes the panel again");
Check(frame.withheld, "the closing chord is still withheld from the game");
frame = controls.Update(Released(), open, kDt);
Check(!frame.withheld, "the game gets the controllers back once everything is released");
}
void MenuClosesAndItsPressStaysOutOfTheGame() {
Controls controls;
bool open = false;
controls.Update(Chord(), open, kDt);
controls.Update(Released(), open, kDt);
auto menu = Released();
menu[0].menu = true;
Frame frame = controls.Update(menu, open, kDt);
Check(!open, "the left menu button closes an open panel");
frame = controls.Update(menu, open, kDt);
Check(frame.withheld, "HOME held across the close does not reach the game");
Check(!open, "a held menu button does not reopen the panel");
frame = controls.Update(Released(), open, kDt);
Check(!frame.withheld, "released, the controllers go back to the game");
frame = controls.Update(menu, open, kDt);
Check(!open && !frame.withheld, "with the panel closed the menu button is the game's");
}
void SelectWaitsForAReleaseAndFollowsTheTrigger() {
Controls controls;
bool open = false;
// Opened from the settings bar while a trigger is held for the game.
auto trigger = Released();
trigger[1].trigger = 1.0f;
controls.Update(trigger, open, kDt);
open = true;
Frame frame = controls.Update(trigger, open, kDt);
Check(frame.open && !frame.select, "a trigger held from before the panel opened does not click");
frame = controls.Update(Released(), open, kDt);
Check(!frame.select, "nothing held, nothing selected");
frame = controls.Update(trigger, open, kDt);
Check(frame.select && frame.pointing_hand == 1, "a fresh right trigger selects and points with the right hand");
auto left = Released();
left[0].trigger = 0.9f;
controls.Update(Released(), open, kDt);
frame = controls.Update(left, open, kDt);
Check(frame.select && frame.pointing_hand == 0, "pulling the left trigger hands the pointer to the left hand");
auto button = Released();
button[1].primary = true;
controls.Update(Released(), open, kDt);
frame = controls.Update(button, open, kDt);
Check(frame.select && frame.pointing_hand == 0, "A selects without moving the pointer to another hand");
}
void ThumbstickScrolls() {
Controls controls;
bool open = true;
controls.Update(Released(), open, kDt);
auto small = Released();
small[1].stick_y = 0.2f;
Check(controls.Update(small, open, kDt).wheel == 0.0f, "a resting thumbstick does not scroll");
auto up = Released();
up[1].stick_y = 1.0f;
CheckNear(controls.Update(up, open, 0.5f).wheel, kScrollStepsPerSecond * 0.1f,
"full deflection scrolls up at the full rate, with a long frame clamped");
auto down = Released();
down[0].stick_y = -1.0f;
down[1].stick_y = 0.3f;
Check(controls.Update(down, open, kDt).wheel < 0.0f, "the more deflected stick decides the direction");
bool closed = false;
Controls idle;
Check(idle.Update(up, closed, kDt).wheel == 0.0f, "a closed panel does not scroll");
}
void HitsMapOntoTheCanvas() {
wii_remote::ScreenHit hit{};
hit.valid = true;
hit.u = -1.0f;
hit.v = 1.0f;
auto point = CanvasPoint(hit);
CheckNear(point[0], 0.0f, "the panel's left edge is canvas x 0");
CheckNear(point[1], 0.0f, "the panel's top edge is canvas y 0");
hit.u = 1.0f;
hit.v = -1.0f;
point = CanvasPoint(hit);
CheckNear(point[0], kSettingsPanelWidthPixels, "the right edge is the canvas width");
CheckNear(point[1], kSettingsPanelHeightPixels, "the bottom edge is the canvas height");
const auto extents = HalfExtents(2.4f);
CheckNear(extents[0], 0.9f, "three quarters of a 2.4 m screen is 1.8 m across");
CheckNear(extents[1], 0.675f, "and keeps the canvas's 4:3");
}
void BridgeAccumulatesWheelUntilTaken() {
OpenXRSetSettingsPanelOpen(true);
Check(OpenXRSettingsPanelOpen(), "the open flag is shared");
OpenXRPublishSettingsPanelPointer(true, 10.0f, 20.0f, false, 0.5f);
OpenXRPublishSettingsPanelPointer(true, 11.0f, 21.0f, true, 0.25f);
OpenXRSettingsPanelPointer pointer = OpenXRTakeSettingsPanelPointer();
Check(pointer.valid && pointer.select, "the latest pointer state is read");
CheckNear(pointer.x, 11.0f, "the latest x is read");
CheckNear(pointer.wheel, 0.75f, "wheel steps from every XR frame are kept");
pointer = OpenXRTakeSettingsPanelPointer();
CheckNear(pointer.wheel, 0.0f, "taking the pointer consumes its wheel steps");
OpenXRSetSettingsPanelOpen(false);
Check(!OpenXRSettingsPanelOpen(), "and can be cleared again");
}
} // namespace
int main() {
ChordOpensAndClosesOnce();
MenuClosesAndItsPressStaysOutOfTheGame();
SelectWaitsForAReleaseAndFollowsTheTrigger();
ThumbstickScrolls();
HitsMapOntoTheCanvas();
BridgeAccumulatesWheelUntilTaken();
if (g_failures != 0) {
std::cerr << g_failures << " check(s) failed\n";
return 1;
}
std::cout << "vr_settings_panel_tests: all checks passed\n";
return 0;
}