mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 05:00:08 +02:00
Pointer helper: one function per main loop section
Structure only, no behaviour change: every frame does the same work in the
same order. main() now sets up and runs a short loop that calls the
sections, each a Pointer member named after its comment:
HeadsetOff, GazeAwake, RelayCommands, ReadFrame, ClaimPulse,
PauseOverlayList, LaserMode, HandRole, LastUsedWins, Recenter, GazeMode,
Hands, GazePrecisionButtons, KeyboardClicks, CalPanelOpened, HeadSteer,
PinchesAndGrips, HandsStopped, MarkHeld, HeadFollow, SlowWork, Cursor,
HeldBackPress, KeyboardClickHold, Click, PollControllerButtons,
SteamVRQuit
- The values the sections share within a frame (the poses, hmdRaw, tnow,
eye, and handOk from PinchesAndGrips for HandsStopped) are in a Frame,
read by ReadFrame at the same point as before. eye moved there from just
after "last used wins"; it only depends on the poses, read once.
Sections open with local aliases (hmd, hm, tnow, eye), so the bodies are
the old code, dedented.
- None of the loop's 43 continues skipped the rest of a frame: 25 were in
the relay's recvfrom loop and 18 in the controller, press, and overlay
scans. The recvfrom loop's body is now Command(), where they're returns.
The quit event's "return 0" is SteamVRQuit() returning true.
- The loop's lambdas are members too: HandAngles, EndHold, StartHold,
Steer, BeginHold, HeadAngles (taking tnow or the HMD pose as a parameter),
and Nearest with its Hit type.
- The cursor section is Cursor(): Tilt() while tilting; otherwise
FindCursor() (collision, drag lock, occlusion; returns a Spot), DrawDot()
(SteamVR Settings, our dot), and SendRay() (the controller pose).
Built with -Wextra -Wshadow too: no warnings. The three shadowing warnings
the old main() had ("from" twice, "now") are gone with the separate scopes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a000725021
commit
7066b591ff
1 file changed
+358
-124
+358
-124
@@ -659,6 +659,17 @@ constexpr double kPlaceDegPerSec = 60; // tested: 40 deg/s is applied exact
|
||||
|
||||
using Clock = std::chrono::steady_clock;
|
||||
|
||||
// One frame's poses and time: ReadFrame reads them after the relay's commands, and the sections
|
||||
// after it share them. handOk: PinchesAndGrips read the hand gestures (for HandsStopped).
|
||||
struct Frame {
|
||||
vr::TrackedDevicePose_t all[vr::k_unMaxTrackedDeviceCount];
|
||||
vr::TrackedDevicePose_t hmdRaw;
|
||||
Clock::time_point tnow;
|
||||
Vec3 eye;
|
||||
bool handOk = false;
|
||||
const vr::TrackedDevicePose_t &Hmd() const { return all[0]; }
|
||||
};
|
||||
|
||||
// Everything the pointer keeps: its settings, what Init sets up, and the state the main loop
|
||||
// carries from one frame to the next.
|
||||
struct Pointer {
|
||||
@@ -813,7 +824,61 @@ struct Pointer {
|
||||
std::string FindPanel(const char *key, Panel &p, Vec3 &eye);
|
||||
void GrabProbe(const char *key);
|
||||
std::string Place(const char *key, Vec3 target, const Basis &bt, double grabBelow);
|
||||
int Run();
|
||||
// Nearest's answer: the nearest overlay along a ray.
|
||||
struct Hit {
|
||||
double along = 1e9;
|
||||
std::string key;
|
||||
bool scene = false;
|
||||
Vec3 point, normal;
|
||||
};
|
||||
// Where the cursor is this frame (FindCursor), for DrawDot and SendRay.
|
||||
struct Spot {
|
||||
bool dragging = false;
|
||||
Vec3 dir, point;
|
||||
double best = 1e9, distance = 0;
|
||||
bool onEdge = false, occluded = false, onScene = false, onPanel = false;
|
||||
};
|
||||
|
||||
// The main loop's sections, in the order main calls them, and what they share.
|
||||
void HeadsetOff();
|
||||
void GazeAwake();
|
||||
void RelayCommands();
|
||||
void Command(const char *buf, const sockaddr_un &sender, socklen_t senderLen);
|
||||
void ReadFrame(Frame &frame);
|
||||
void ClaimPulse(const Frame &frame);
|
||||
void PauseOverlayList();
|
||||
void LaserMode();
|
||||
void HandRole(const Frame &frame);
|
||||
void LastUsedWins(const Frame &frame);
|
||||
void Recenter(const Frame &frame);
|
||||
void GazeMode(const Frame &frame);
|
||||
void Hands(const Frame &frame);
|
||||
bool HandAngles(const float p[3], uint64_t t_ns, const Vec3 &from, double &hy, double &hp);
|
||||
void EndHold(bool lost);
|
||||
void StartHold(bool press, Clock::time_point tnow);
|
||||
void Steer(double hy, double hp, double deadzone, double gain, Clock::time_point tnow);
|
||||
void BeginHold(int side, bool grip, const fh_pinch_t &g, Clock::time_point tnow);
|
||||
void GazePrecisionButtons(const Frame &frame);
|
||||
bool HeadAngles(const vr::TrackedDevicePose_t &hmd, double &hy, double &hp);
|
||||
void KeyboardClicks(const Frame &frame);
|
||||
void CalPanelOpened();
|
||||
void HeadSteer(const Frame &frame);
|
||||
void PinchesAndGrips(Frame &frame);
|
||||
void HandsStopped(const Frame &frame);
|
||||
void MarkHeld(const Frame &frame);
|
||||
void HeadFollow(const Frame &frame);
|
||||
void SlowWork();
|
||||
void Cursor(const Frame &frame);
|
||||
void Tilt(const Frame &frame);
|
||||
Hit Nearest(Vec3 from, Vec3 d);
|
||||
Spot FindCursor(const Frame &frame);
|
||||
void DrawDot(const Frame &frame, const Spot &spot);
|
||||
void SendRay(const Frame &frame, const Spot &spot);
|
||||
void HeldBackPress(const Frame &frame);
|
||||
void KeyboardClickHold(const Frame &frame);
|
||||
void Click(const Frame &frame);
|
||||
void PollControllerButtons();
|
||||
bool SteamVRQuit();
|
||||
};
|
||||
|
||||
// After cfg.Load: a changed POINTER_FOLLOW or POINTER_GAZE turns head follow or gaze mode on or off.
|
||||
@@ -1206,12 +1271,11 @@ std::string Pointer::Place(const char *key, Vec3 target, const Basis &bt, double
|
||||
}
|
||||
|
||||
|
||||
int Pointer::Run() {
|
||||
while (true) {
|
||||
// The headset off: SteamVR drops the HMD's activity to idle as soon as it comes off.
|
||||
// An awake pointer (a connected controller, SteamVR's laser mode forced on) kept the
|
||||
// displays from sleeping, so it's released at once, and the mouse can't wake it until
|
||||
// the headset is back on (then the first mouse input does).
|
||||
// The headset off: SteamVR drops the HMD's activity to idle as soon as it comes off.
|
||||
// An awake pointer (a connected controller, SteamVR's laser mode forced on) kept the
|
||||
// displays from sleeping, so it's released at once, and the mouse can't wake it until
|
||||
// the headset is back on (then the first mouse input does).
|
||||
void Pointer::HeadsetOff() {
|
||||
const auto level = sys->GetTrackedDeviceActivityLevel(vr::k_unTrackedDeviceIndex_Hmd);
|
||||
headsetOff = level == vr::k_EDeviceActivityLevel_Idle || level == vr::k_EDeviceActivityLevel_Standby ||
|
||||
level == vr::k_EDeviceActivityLevel_Idle_Timeout;
|
||||
@@ -1225,9 +1289,10 @@ int Pointer::Run() {
|
||||
std::printf("headset off: pointer released\n");
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
// Outside games, gaze mode keeps the pointer (see the top); the relay needs to know.
|
||||
{
|
||||
// Outside games, gaze mode keeps the pointer (see the top); the relay needs to know.
|
||||
void Pointer::GazeAwake() {
|
||||
const auto t = Clock::now();
|
||||
if (t - inGameAt > std::chrono::milliseconds(500)) {
|
||||
inGameAt = t;
|
||||
@@ -1245,9 +1310,10 @@ int Pointer::Run() {
|
||||
gazeAwakeAt = t;
|
||||
SendTo(out, "frametop_relay", awake ? "gazeawake 1" : "gazeawake 0");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Commands from the relay.
|
||||
// Commands from the relay.
|
||||
void Pointer::RelayCommands() {
|
||||
char buf[256];
|
||||
ssize_t n;
|
||||
sockaddr_un from{};
|
||||
@@ -1258,6 +1324,12 @@ int Pointer::Run() {
|
||||
const sockaddr_un sender = from;
|
||||
const socklen_t senderLen = fromLen;
|
||||
fromLen = sizeof from;
|
||||
Command(buf, sender, senderLen);
|
||||
}
|
||||
}
|
||||
|
||||
// One command from the relay (or another sender: reply answers it).
|
||||
void Pointer::Command(const char *buf, const sockaddr_un &sender, socklen_t senderLen) {
|
||||
auto reply = [&](const std::string &msg) {
|
||||
if (senderLen > offsetof(sockaddr_un, sun_path))
|
||||
sendto(out, msg.data(), msg.size(), 0, reinterpret_cast<const sockaddr *>(&sender), senderLen);
|
||||
@@ -1266,7 +1338,7 @@ int Pointer::Run() {
|
||||
double g[4];
|
||||
if (std::sscanf(buf, "gz %lf %lf %lf %lf", &g[0], &g[1], &g[2], &g[3]) == 4) {
|
||||
gz = {g[0], g[1], g[2], g[3], Clock::now()};
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
// The gaze calibration panel (see the top): presses answer it instead of clicking.
|
||||
{
|
||||
@@ -1275,7 +1347,7 @@ int Pointer::Run() {
|
||||
const bool was = Clock::now() < calPanelUntil;
|
||||
calPanelUntil = on ? Clock::now() + std::chrono::seconds(3) : Clock::time_point{};
|
||||
if (on && !was) calOpened = true;
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (Clock::now() < calPanelUntil) {
|
||||
@@ -1283,11 +1355,11 @@ int Pointer::Run() {
|
||||
const bool quit = !std::strncmp(buf, "btn b 1", 7) || !std::strncmp(buf, "gazekey right 1", 15);
|
||||
if (accept || quit) {
|
||||
SendTo(out, "ft_gazed", accept ? "calaccept" : "calquit");
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (!std::strncmp(buf, "btn ", 4) || !std::strncmp(buf, "gazekey ", 8) ||
|
||||
!std::strncmp(buf, "precision ", 10) || !std::strncmp(buf, "gazedrag ", 9))
|
||||
continue; // their releases, and the other buttons: nothing to click now
|
||||
return; // their releases, and the other buttons: nothing to click now
|
||||
}
|
||||
{
|
||||
char kind[16], source[16];
|
||||
@@ -1297,39 +1369,39 @@ int Pointer::Run() {
|
||||
lastMouse = Clock::now();
|
||||
if (!active) Wake(Clock::now());
|
||||
devicePresses.push_back({source, !std::strcmp(kind, "gazedrag"), v != 0});
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::sscanf(buf, "gazekey %15s %d", source, &v) == 2 &&
|
||||
(!std::strcmp(source, "left") || !std::strcmp(source, "right"))) {
|
||||
lastMouse = Clock::now();
|
||||
if (!active) Wake(Clock::now());
|
||||
keyPresses.push_back({!std::strcmp(source, "right"), v != 0});
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (std::strcmp(buf, "typing") == 0) { // not mouse input: it never wakes the pointer
|
||||
lastTyping = Clock::now();
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::strncmp(buf, "vrbind", 6) == 0) {
|
||||
std::printf("controller buttons: %s\n", controllerButtons.Bind(buf + 6).c_str());
|
||||
std::fflush(stdout);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::strncmp(buf, "vrglobal", 8) == 0) {
|
||||
const char *arg = buf + 8;
|
||||
while (*arg == ' ') ++arg;
|
||||
ControllerButtons::SetGlobal(std::strncmp(arg, "off", 3) != 0);
|
||||
reply(controllerButtons.Status());
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::strncmp(buf, "vrstatus", 8) == 0) {
|
||||
reply(controllerButtons.Status());
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::strncmp(buf, "overlays", 8) == 0) {
|
||||
overlays.Request(sender, senderLen); // answered from the list's thread
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::strncmp(buf, "gaze", 4) == 0) {
|
||||
const char *arg = buf + 4;
|
||||
@@ -1343,7 +1415,7 @@ int Pointer::Run() {
|
||||
std::fflush(stdout);
|
||||
}
|
||||
reply(gazeOn ? "ok on" : "ok off");
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
const bool mouseInput = std::strncmp(buf, "move", 4) == 0 || std::strncmp(buf, "btn", 3) == 0 ||
|
||||
std::strncmp(buf, "scroll", 6) == 0;
|
||||
@@ -1353,18 +1425,18 @@ int Pointer::Run() {
|
||||
if (mouseInput && !moveHeldBack) lastMouse = Clock::now();
|
||||
// Any mouse input wakes the pointer (after a controller took over, or a helper restart).
|
||||
if (!active && mouseInput) Wake(Clock::now());
|
||||
if (moveHeldBack) continue;
|
||||
if (moveHeldBack) return;
|
||||
double a, b;
|
||||
char key[128];
|
||||
double px, py, pz, pyaw, ppitch, proll = 0, pgrab = -1;
|
||||
if (std::sscanf(buf, "grabprobe %127s", key) == 1) {
|
||||
GrabProbe(key);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::sscanf(buf, "place %127s %lf %lf %lf %lf %lf %lf %lf", key, &px, &py, &pz, &pyaw, &ppitch, &proll,
|
||||
&pgrab) >= 6) {
|
||||
reply(Place(key, {px, py, pz}, PanelBasis(pyaw, ppitch, proll), pgrab >= 0 ? pgrab : cfg.grabOffset));
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::sscanf(buf, "measure %127s", key) == 1) {
|
||||
Panel p;
|
||||
@@ -1377,7 +1449,7 @@ int Pointer::Run() {
|
||||
p.basis.x.z, p.basis.y.x, p.basis.y.y, p.basis.y.z, p.basis.z.x, p.basis.z.y,
|
||||
p.basis.z.z);
|
||||
reply(err.empty() ? msg : "error " + err);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::strncmp(buf, "head", 4) == 0) {
|
||||
vr::TrackedDevicePose_t h;
|
||||
@@ -1388,35 +1460,35 @@ int Pointer::Run() {
|
||||
std::snprintf(msg, sizeof msg, "ok %.4f %.4f %.4f %.2f %.2f", e.x, e.y, e.z,
|
||||
std::atan2(-f.x, -f.z) * 180 / M_PI, std::asin(std::clamp(f.y, -1.0, 1.0)) * 180 / M_PI);
|
||||
reply(h.bPoseIsValid ? msg : "error no head pose (headset off?)");
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::strncmp(buf, "debug", 5) == 0) {
|
||||
debug = !debug;
|
||||
std::printf("debug %s\n", debug ? "on" : "off");
|
||||
std::fflush(stdout);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::strncmp(buf, "btn trigger 1", 13) == 0) {
|
||||
LeftButton(true);
|
||||
continue;
|
||||
return;
|
||||
} else if (std::strncmp(buf, "btn trigger 0", 13) == 0) {
|
||||
LeftButton(false);
|
||||
continue;
|
||||
return;
|
||||
} else if (std::strncmp(buf, "btn b 1", 7) == 0 && RightButton(true)) {
|
||||
continue;
|
||||
return;
|
||||
} else if (std::strncmp(buf, "btn b 0", 7) == 0 && RightButton(false)) {
|
||||
continue;
|
||||
return;
|
||||
} else if (std::strncmp(buf, "btn b 1", 7) == 0 && leftHeld) {
|
||||
tilting = tiltStart = swallowedRight = true; // right press while dragging: tilt, no right-click
|
||||
continue;
|
||||
return;
|
||||
} else if (std::strncmp(buf, "btn b 0", 7) == 0 && swallowedRight) {
|
||||
tilting = swallowedRight = false;
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (tilting && std::sscanf(buf, "move %lf %lf", &a, &b) == 2) {
|
||||
tiltYaw += a;
|
||||
tiltPitch = std::clamp(tiltPitch + b, -80.0, 80.0);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
if (std::sscanf(buf, "move %lf %lf", &a, &b) == 2) {
|
||||
lastMove = Clock::now(); // the dot shows while the mouse moves it (gaze mode)
|
||||
@@ -1462,16 +1534,20 @@ int Pointer::Run() {
|
||||
} else {
|
||||
SendTo(out, "ft_pointer", buf); // btn, scroll
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vr::TrackedDevicePose_t all[vr::k_unMaxTrackedDeviceCount];
|
||||
sys->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, 0.011f, all, vr::k_unMaxTrackedDeviceCount);
|
||||
const vr::TrackedDevicePose_t &hmd = all[0];
|
||||
vr::TrackedDevicePose_t hmdRaw;
|
||||
sys->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseRawAndUncalibrated, 0.011f, &hmdRaw, 1);
|
||||
const auto tnow = Clock::now();
|
||||
// This frame's poses and time (see Frame).
|
||||
void Pointer::ReadFrame(Frame &frame) {
|
||||
sys->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, 0.011f, frame.all, vr::k_unMaxTrackedDeviceCount);
|
||||
sys->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseRawAndUncalibrated, 0.011f, &frame.hmdRaw, 1);
|
||||
frame.tnow = Clock::now();
|
||||
const auto &hm = frame.Hmd().mDeviceToAbsoluteTracking.m;
|
||||
frame.eye = {hm[0][3], hm[1][3], hm[2][3]};
|
||||
}
|
||||
|
||||
// Claim pulse (switchlaserhand on the driver's "a" button, no click).
|
||||
// Claim pulse (switchlaserhand on the driver's "a" button, no click).
|
||||
void Pointer::ClaimPulse(const Frame &frame) {
|
||||
const auto tnow = frame.tnow;
|
||||
if (claimPending && tnow >= claimAt) {
|
||||
SendTo(out, "ft_pointer", "btn a 1");
|
||||
claimPending = false;
|
||||
@@ -1481,11 +1557,15 @@ int Pointer::Run() {
|
||||
SendTo(out, "ft_pointer", "btn a 0");
|
||||
claimHeld = false;
|
||||
}
|
||||
}
|
||||
|
||||
// The overlay list is only needed while the pointer is awake (see OverlayList).
|
||||
// The overlay list is only needed while the pointer is awake (see OverlayList).
|
||||
void Pointer::PauseOverlayList() {
|
||||
overlays.SetPaused(!active || headsetOff);
|
||||
}
|
||||
|
||||
// Laser mode on while the pointer is awake.
|
||||
// Laser mode on while the pointer is awake.
|
||||
void Pointer::LaserMode() {
|
||||
if (active != laserModeShown) {
|
||||
laserModeShown = active;
|
||||
if (active) overlay->ShowOverlay(laserMode);
|
||||
@@ -1493,8 +1573,11 @@ int Pointer::Run() {
|
||||
if (debug) std::printf("laser mode %s\n", active ? "forced on" : "released");
|
||||
if (debug) std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
// Didn't get the hand role (a held controller keeps it): release, back off.
|
||||
// Didn't get the hand role (a held controller keeps it): release, back off.
|
||||
void Pointer::HandRole(const Frame &frame) {
|
||||
const auto tnow = frame.tnow;
|
||||
if (active && tnow - wokeAt > std::chrono::seconds(1) && ours != vr::k_unTrackedDeviceIndexInvalid &&
|
||||
sys->GetControllerRoleForTrackedDeviceIndex(ours) == vr::TrackedControllerRole_Invalid) {
|
||||
active = false;
|
||||
@@ -1507,9 +1590,13 @@ int Pointer::Run() {
|
||||
std::printf("no hand role (a controller is in use): pointer released\n");
|
||||
std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
// Last used wins: a real controller being moved releases the pointer (see the top),
|
||||
// in gaze mode too.
|
||||
// Last used wins: a real controller being moved releases the pointer (see the top),
|
||||
// in gaze mode too.
|
||||
void Pointer::LastUsedWins(const Frame &frame) {
|
||||
const auto &all = frame.all;
|
||||
const auto tnow = frame.tnow;
|
||||
if (active && hold.src == Src::None && tnow - lastMouse > std::chrono::milliseconds(500)) {
|
||||
for (vr::TrackedDeviceIndex_t i = 1; i < vr::k_unMaxTrackedDeviceCount; ++i) {
|
||||
if (i == ours || !all[i].bPoseIsValid || all[i].eTrackingResult != vr::TrackingResult_Running_OK ||
|
||||
@@ -1540,9 +1627,14 @@ int Pointer::Run() {
|
||||
} else {
|
||||
std::fill(std::begin(movingSince), std::end(movingSince), Clock::time_point{});
|
||||
}
|
||||
const auto &hm = hmd.mDeviceToAbsoluteTracking.m;
|
||||
const Vec3 eye{hm[0][3], hm[1][3], hm[2][3]};
|
||||
}
|
||||
|
||||
// A pending recenter (the command, a wake, a first move): the anchor goes to the eye, aimed where the
|
||||
// head faces.
|
||||
void Pointer::Recenter(const Frame &frame) {
|
||||
const vr::TrackedDevicePose_t &hmd = frame.Hmd();
|
||||
const auto &hm = hmd.mDeviceToAbsoluteTracking.m;
|
||||
const Vec3 &eye = frame.eye;
|
||||
if (recenter && hmd.bPoseIsValid) {
|
||||
anchor = eye;
|
||||
const Vec3 f{-hm[0][2], -hm[1][2], -hm[2][2]};
|
||||
@@ -1552,9 +1644,14 @@ int Pointer::Run() {
|
||||
recenter = false;
|
||||
followReset = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Gaze mode (see the top): the gaze has the pointer, or takes it back when you look
|
||||
// well away from it. Not while a press holds the pointer, and only on fresh gaze.
|
||||
// Gaze mode (see the top): the gaze has the pointer, or takes it back when you look
|
||||
// well away from it. Not while a press holds the pointer, and only on fresh gaze.
|
||||
void Pointer::GazeMode(const Frame &frame) {
|
||||
const vr::TrackedDevicePose_t &hmd = frame.Hmd();
|
||||
const auto tnow = frame.tnow;
|
||||
const Vec3 &eye = frame.eye;
|
||||
if (hmd.bPoseIsValid) lastHead = hmd.mDeviceToAbsoluteTracking, haveHead = true;
|
||||
if (gazeOn && active && hmd.bPoseIsValid && !tilting && !leftHeld && !aimHeld && !clickPress && !clickRelease &&
|
||||
tnow >= dropHoldUntil &&
|
||||
@@ -1577,23 +1674,27 @@ int Pointer::Run() {
|
||||
pitch = std::clamp(std::asin(std::clamp(g.y, -1.0, 1.0)) * 180 / M_PI, -85.0, 85.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Hands (see the top): pinches and grips from ft-hands.
|
||||
{
|
||||
// Hands (see the top): pinches and grips from ft-hands.
|
||||
void Pointer::Hands(const Frame &frame) {
|
||||
const auto tnow = frame.tnow;
|
||||
vr::TrackedDevicePose_t h0;
|
||||
sys->GetDeviceToAbsoluteTrackingPose(vr::TrackingUniverseStanding, 0, &h0, 1);
|
||||
if (h0.bPoseIsValid) poses.Add(tnow, h0.mDeviceToAbsoluteTracking);
|
||||
}
|
||||
// Where a gesture's point is, seen from the hold's origin: yaw and pitch, degrees.
|
||||
auto handAngles = [&](const float p[3], uint64_t t_ns, const Vec3 &from, double &hy, double &hp) {
|
||||
}
|
||||
|
||||
// Where a gesture's point is, seen from the hold's origin: yaw and pitch, degrees.
|
||||
bool Pointer::HandAngles(const float p[3], uint64_t t_ns, const Vec3 &from, double &hy, double &hp) {
|
||||
vr::HmdMatrix34_t head;
|
||||
if (!poses.At(t_ns, head)) return false;
|
||||
const Vec3 d = Normalize(Position(head) + Rotate(head, Vec3{p[0], p[1], p[2]}) - from);
|
||||
hy = std::atan2(-d.x, -d.z) * 180 / M_PI;
|
||||
hp = std::asin(std::clamp(d.y, -1.0, 1.0)) * 180 / M_PI;
|
||||
return true;
|
||||
};
|
||||
auto endHold = [&](bool lost) {
|
||||
}
|
||||
|
||||
void Pointer::EndHold(bool lost) {
|
||||
if (hold.pressed) {
|
||||
if (leftHeld) ReleaseLeft();
|
||||
} else if (aimHeld) {
|
||||
@@ -1613,10 +1714,11 @@ int Pointer::Run() {
|
||||
lost ? "lost" : "released", hold.pressed ? "" : lost ? " (no click)" : " (click)");
|
||||
if (debug) std::fflush(stdout);
|
||||
hold = {};
|
||||
};
|
||||
// The press, once a hold's source is set: a real press (dragging until the release), or
|
||||
// held back like gaze mode's mouse press (see the top): the click comes on the release.
|
||||
auto startHold = [&](bool press) {
|
||||
}
|
||||
|
||||
// The press, once a hold's source is set: a real press (dragging until the release), or
|
||||
// held back like gaze mode's mouse press (see the top): the click comes on the release.
|
||||
void Pointer::StartHold(bool press, Clock::time_point tnow) {
|
||||
hold.startYaw = hold.lastYaw = yaw, hold.startPitch = hold.lastPitch = pitch;
|
||||
hold.pressed = press;
|
||||
if (press) {
|
||||
@@ -1630,10 +1732,11 @@ int Pointer::Run() {
|
||||
aimHeld = aimHand = true;
|
||||
pulseAt = tnow;
|
||||
}
|
||||
};
|
||||
// The hold's source moved to (hy, hp): past the dead zone, the pointer follows at the gain,
|
||||
// from where it was when it got past.
|
||||
auto steer = [&](double hy, double hp, double deadzone, double gain) {
|
||||
}
|
||||
|
||||
// The hold's source moved to (hy, hp): past the dead zone, the pointer follows at the gain,
|
||||
// from where it was when it got past.
|
||||
void Pointer::Steer(double hy, double hp, double deadzone, double gain, Clock::time_point tnow) {
|
||||
const double dy = std::remainder(hy - hold.refYaw, 360.0), dp = hp - hold.refPitch;
|
||||
if (!hold.engaged) {
|
||||
if (std::hypot(dy, dp) <= deadzone) return;
|
||||
@@ -1652,8 +1755,9 @@ int Pointer::Run() {
|
||||
nudgeMoved += std::hypot(std::remainder(yaw - hold.lastYaw, 360.0), pitch - hold.lastPitch);
|
||||
hold.lastYaw = yaw, hold.lastPitch = pitch;
|
||||
lastMove = tnow; // the dot shows while it moves (gaze mode)
|
||||
};
|
||||
auto beginHold = [&](int side, bool grip, const fh_pinch_t &g) {
|
||||
}
|
||||
|
||||
void Pointer::BeginHold(int side, bool grip, const fh_pinch_t &g, Clock::time_point tnow) {
|
||||
handUsed = tnow;
|
||||
if (hold.src != Src::None) {
|
||||
// A grip takes over a pinch on the way to it (closing the hand passes through
|
||||
@@ -1683,38 +1787,49 @@ int Pointer::Run() {
|
||||
}
|
||||
Hold h;
|
||||
h.src = Src::Hand, h.side = side, h.grip = grip, h.origin = Position(head);
|
||||
if (!handAngles(g.begin_point, g.begin_ns, h.origin, h.refYaw, h.refPitch)) return;
|
||||
if (!HandAngles(g.begin_point, g.begin_ns, h.origin, h.refYaw, h.refPitch)) return;
|
||||
hold = h;
|
||||
// A grip, or a pinch without gaze mode: a real press where the pointer is (where
|
||||
// you look, in gaze mode), dragging with the hand until it opens.
|
||||
startHold(grip || !gazeOn);
|
||||
StartHold(grip || !gazeOn, tnow);
|
||||
if (debug) std::printf("hand %s %s began\n", side ? "right" : "left", grip ? "grip" : "pinch");
|
||||
if (debug) std::fflush(stdout);
|
||||
};
|
||||
// Gaze precision and gaze drag buttons (see the top): the mouse steers.
|
||||
}
|
||||
|
||||
// Gaze precision and gaze drag buttons (see the top): the mouse steers.
|
||||
void Pointer::GazePrecisionButtons(const Frame &frame) {
|
||||
const auto tnow = frame.tnow;
|
||||
for (const DevicePress &p : devicePresses) {
|
||||
if (p.source == "left" || p.source == "right") continue; // no controllers in gaze mode
|
||||
if (!p.down) {
|
||||
if (hold.src == Src::Mouse) endHold(false);
|
||||
if (hold.src == Src::Mouse) EndHold(false);
|
||||
continue;
|
||||
}
|
||||
if (hold.src != Src::None || leftHeld || aimHeld || clickPress || clickRelease) continue;
|
||||
Hold h;
|
||||
h.src = Src::Mouse, h.grip = p.drag;
|
||||
hold = h;
|
||||
startHold(p.drag);
|
||||
StartHold(p.drag, tnow);
|
||||
if (debug) std::printf("hold gaze %s began (%s)\n", p.drag ? "drag" : "precision", p.source.c_str());
|
||||
if (debug) std::fflush(stdout);
|
||||
}
|
||||
devicePresses.clear();
|
||||
// Keyboard clicks (see the top): held back at the gaze, steered by the head.
|
||||
auto headAngles = [&](double &hy, double &hp) {
|
||||
}
|
||||
|
||||
// Where the head faces: yaw and pitch, degrees.
|
||||
bool Pointer::HeadAngles(const vr::TrackedDevicePose_t &hmd, double &hy, double &hp) {
|
||||
const auto &hm = hmd.mDeviceToAbsoluteTracking.m;
|
||||
if (!hmd.bPoseIsValid) return false;
|
||||
const Vec3 f = Normalize({-hm[0][2], -hm[1][2], -hm[2][2]});
|
||||
hy = std::atan2(-f.x, -f.z) * 180 / M_PI;
|
||||
hp = std::asin(std::clamp(f.y, -1.0, 1.0)) * 180 / M_PI;
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
// Keyboard clicks (see the top): held back at the gaze, steered by the head.
|
||||
void Pointer::KeyboardClicks(const Frame &frame) {
|
||||
const vr::TrackedDevicePose_t &hmd = frame.Hmd();
|
||||
const auto tnow = frame.tnow;
|
||||
for (const KeyPress &k : keyPresses) {
|
||||
(k.right ? keyRightDown : keyLeftDown) = k.down;
|
||||
if (!k.down) {
|
||||
@@ -1733,7 +1848,7 @@ int Pointer::Run() {
|
||||
nudgeMoved = 0;
|
||||
confirmLesson = true;
|
||||
}
|
||||
endHold(false);
|
||||
EndHold(false);
|
||||
keyTilting = false;
|
||||
}
|
||||
continue;
|
||||
@@ -1741,7 +1856,7 @@ int Pointer::Run() {
|
||||
if (k.right && hold.src == Src::Head && !hold.right && hold.pressed && leftHeld) {
|
||||
// gaze_right during a gaze_left drag: tilt while it's held (see the top).
|
||||
tilting = tiltStart = keyTilting = true;
|
||||
headAngles(keyTiltYaw, keyTiltPitch);
|
||||
HeadAngles(hmd, keyTiltYaw, keyTiltPitch);
|
||||
if (debug) std::printf("hold key left: tilt began\n");
|
||||
if (debug) std::fflush(stdout);
|
||||
continue;
|
||||
@@ -1751,7 +1866,7 @@ int Pointer::Run() {
|
||||
// (the correction is a lesson), then the head drags.
|
||||
aimHeld = aimHand = false;
|
||||
hold.pressed = hold.grip = hold.keyDrag = true, hold.promote = false, hold.engaged = false;
|
||||
headAngles(hold.refYaw, hold.refPitch);
|
||||
HeadAngles(hmd, hold.refYaw, hold.refPitch);
|
||||
gazeBack = gazeOn;
|
||||
pressRight = false;
|
||||
PressLeft();
|
||||
@@ -1762,32 +1877,47 @@ int Pointer::Run() {
|
||||
if (hold.src != Src::None || leftHeld || aimHeld || clickPress || clickRelease) continue;
|
||||
Hold h;
|
||||
h.src = Src::Head, h.promote = true, h.right = k.right, h.pressYaw = yaw, h.pressPitch = pitch;
|
||||
if (!headAngles(h.refYaw, h.refPitch)) continue;
|
||||
if (!HeadAngles(hmd, h.refYaw, h.refPitch)) continue;
|
||||
hold = h;
|
||||
startHold(false);
|
||||
StartHold(false, tnow);
|
||||
if (debug) std::printf("hold key %s began\n", k.right ? "right" : "left");
|
||||
if (debug) std::fflush(stdout);
|
||||
}
|
||||
keyPresses.clear();
|
||||
}
|
||||
|
||||
void Pointer::CalPanelOpened() {
|
||||
if (calOpened) { // the calibration panel came up: a press in progress ends, no click
|
||||
calOpened = false;
|
||||
if (hold.src != Src::None) endHold(true);
|
||||
if (hold.src != Src::None) EndHold(true);
|
||||
if (leftHeld) ReleaseLeft();
|
||||
aimHeld = aimHand = clickPress = false;
|
||||
}
|
||||
}
|
||||
|
||||
// A keyboard click's hold: the head steers the pointer, or turns the panel in a tilt.
|
||||
void Pointer::HeadSteer(const Frame &frame) {
|
||||
const vr::TrackedDevicePose_t &hmd = frame.Hmd();
|
||||
const auto tnow = frame.tnow;
|
||||
if (hold.src == Src::Head) {
|
||||
double hy, hp;
|
||||
if (headAngles(hy, hp) && keyTilting) {
|
||||
if (HeadAngles(hmd, hy, hp) && keyTilting) {
|
||||
// The head turns the panel, as the mouse does in a tilt; the pointer stays put.
|
||||
tiltYaw += std::remainder(hy - keyTiltYaw, 360.0);
|
||||
tiltPitch = std::clamp(tiltPitch + hp - keyTiltPitch, -80.0, 80.0);
|
||||
keyTiltYaw = hy, keyTiltPitch = hp;
|
||||
} else if (headAngles(hy, hp)) {
|
||||
steer(hy, hp, hold.pressed ? 0.3 : cfg.headDeadzone, 1.0);
|
||||
} else if (HeadAngles(hmd, hy, hp)) {
|
||||
Steer(hy, hp, hold.pressed ? 0.3 : cfg.headDeadzone, 1.0, tnow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pinches and grips from ft-hands (see "Hands" at the top), read every frame.
|
||||
void Pointer::PinchesAndGrips(Frame &frame) {
|
||||
const auto tnow = frame.tnow;
|
||||
fh_gestures_t hg;
|
||||
const bool handOk = cfg.handsOn && handFile.Read(hg);
|
||||
frame.handOk = handOk;
|
||||
if (handOk && (hg.seq != handSeq || handFile.opens != handOpens)) {
|
||||
handSeq = hg.seq;
|
||||
handPublished = hg.publish_ns;
|
||||
@@ -1798,7 +1928,7 @@ int Pointer::Run() {
|
||||
for (int s = 0; s < 2; ++s)
|
||||
rebase = rebase || slots[k][s].begins < seenBegins[k][s] || slots[k][s].ends < seenEnds[k][s];
|
||||
if (rebase) {
|
||||
if (hold.src == Src::Hand) endHold(true);
|
||||
if (hold.src == Src::Hand) EndHold(true);
|
||||
for (int k = 0; k < 2; ++k)
|
||||
for (int s = 0; s < 2; ++s) seenBegins[k][s] = slots[k][s].begins, seenEnds[k][s] = slots[k][s].ends;
|
||||
handBaseline = true, handOpens = handFile.opens;
|
||||
@@ -1812,11 +1942,11 @@ int Pointer::Run() {
|
||||
const bool lost = g.flags & FH_PINCH_LOST;
|
||||
seenBegins[k][s] = g.begins, seenEnds[k][s] = g.ends;
|
||||
auto holding = [&] { return hold.src == Src::Hand && hold.side == s && hold.grip == (k == 1); };
|
||||
if (holding() && ended) endHold(lost); // the one held ended (another may have begun)
|
||||
if (holding() && ended) EndHold(lost); // the one held ended (another may have begun)
|
||||
if (began && allowed) {
|
||||
beginHold(s, k == 1, g);
|
||||
BeginHold(s, k == 1, g, tnow);
|
||||
// begun and ended since the last read (a quick tap): its release too
|
||||
if (!(g.flags & FH_PINCH_DOWN) && holding()) endHold(lost);
|
||||
if (!(g.flags & FH_PINCH_DOWN) && holding()) EndHold(lost);
|
||||
}
|
||||
}
|
||||
// The held gesture's hand moves the pointer: past the dead zone (a tap's jitter,
|
||||
@@ -1824,20 +1954,35 @@ int Pointer::Run() {
|
||||
if (hold.src == Src::Hand) {
|
||||
const fh_pinch_t &g = hold.grip ? hg.grip[hold.side] : hg.pinch[hold.side];
|
||||
double hy, hp;
|
||||
if ((g.flags & FH_PINCH_TRACKED) && handAngles(g.point, hg.capture_ns, hold.origin, hy, hp))
|
||||
steer(hy, hp, hold.grip ? 0.5 : cfg.pinchDeadzone, hold.grip ? cfg.gripGain : cfg.pinchGain);
|
||||
if ((g.flags & FH_PINCH_TRACKED) && HandAngles(g.point, hg.capture_ns, hold.origin, hy, hp))
|
||||
Steer(hy, hp, hold.grip ? 0.5 : cfg.pinchDeadzone, hold.grip ? cfg.gripGain : cfg.pinchGain, tnow);
|
||||
}
|
||||
}
|
||||
// ft-hands stopped (or hung) with a gesture held: it's over, as lost.
|
||||
}
|
||||
|
||||
// ft-hands stopped (or hung) with a gesture held: it's over, as lost.
|
||||
void Pointer::HandsStopped(const Frame &frame) {
|
||||
const auto tnow = frame.tnow;
|
||||
const bool handOk = frame.handOk;
|
||||
const uint64_t nowNs =
|
||||
uint64_t(std::chrono::duration_cast<std::chrono::nanoseconds>(tnow.time_since_epoch()).count());
|
||||
if (hold.src == Src::Hand && (!handOk || !active || nowNs - handPublished > 1'500'000'000ull)) endHold(true);
|
||||
if (hold.src != Src::None && hold.src != Src::Hand && !active) endHold(true);
|
||||
if (hold.src == Src::Hand && (!handOk || !active || nowNs - handPublished > 1'500'000'000ull)) EndHold(true);
|
||||
if (hold.src != Src::None && hold.src != Src::Hand && !active) EndHold(true);
|
||||
}
|
||||
|
||||
// A press holds the pointer (gaze mode's dot shows meanwhile, see the top).
|
||||
void Pointer::MarkHeld(const Frame &frame) {
|
||||
const auto tnow = frame.tnow;
|
||||
if (aimHeld || leftHeld || clickPress || clickRelease) lastHeld = tnow;
|
||||
}
|
||||
|
||||
// Head follow (see the top): past the leash (for the delay), ease the reference to the
|
||||
// head's facing, and turn the cursor with it. Not in gaze mode: the gaze places it.
|
||||
// Head follow (see the top): past the leash (for the delay), ease the reference to the
|
||||
// head's facing, and turn the cursor with it. Not in gaze mode: the gaze places it.
|
||||
void Pointer::HeadFollow(const Frame &frame) {
|
||||
const vr::TrackedDevicePose_t &hmd = frame.Hmd();
|
||||
const auto &hm = hmd.mDeviceToAbsoluteTracking.m;
|
||||
const auto tnow = frame.tnow;
|
||||
const Vec3 &eye = frame.eye;
|
||||
if (follow && !gazeOn && active && anchored && hmd.bPoseIsValid) {
|
||||
const Vec3 head = LimitPitch(Vec3{-hm[0][2], -hm[1][2], -hm[2][2]}, 85);
|
||||
if (followReset) {
|
||||
@@ -1877,8 +2022,10 @@ int Pointer::Run() {
|
||||
followRef = ref;
|
||||
}
|
||||
followAt = tnow;
|
||||
}
|
||||
|
||||
// Slow work, once a second: overlay handles, our device index, laser width.
|
||||
// Slow work, once a second: overlay handles, our device index, laser width.
|
||||
void Pointer::SlowWork() {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
if (now - lastSlow > std::chrono::seconds(1)) {
|
||||
lastSlow = now;
|
||||
@@ -1915,8 +2062,24 @@ int Pointer::Run() {
|
||||
visible.clear();
|
||||
for (const auto &[key, h] : handles) visible[key] = overlay->IsOverlayVisible(h);
|
||||
}
|
||||
}
|
||||
|
||||
// The cursor: tilting a grabbed panel, or pointing (find the spot, draw the dot, send the ray).
|
||||
void Pointer::Cursor(const Frame &frame) {
|
||||
const vr::TrackedDevicePose_t &hmd = frame.Hmd();
|
||||
if (active && anchored && hmd.bPoseIsValid && tilting) {
|
||||
Tilt(frame);
|
||||
} else if (active && anchored && hmd.bPoseIsValid) {
|
||||
const Spot spot = FindCursor(frame);
|
||||
DrawDot(frame, spot);
|
||||
SendRay(frame, spot);
|
||||
}
|
||||
}
|
||||
|
||||
void Pointer::Tilt(const Frame &frame) {
|
||||
const vr::TrackedDevicePose_t &hmd = frame.Hmd();
|
||||
const Vec3 &eye = frame.eye;
|
||||
const vr::TrackedDevicePose_t &hmdRaw = frame.hmdRaw;
|
||||
// Rotate the device around the grab point; the grabbed panel turns with it.
|
||||
if (tiltStart) {
|
||||
pivot = lastPoint;
|
||||
@@ -1940,18 +2103,10 @@ int Pointer::Run() {
|
||||
std::snprintf(msg, sizeof msg, "posq %.5f %.5f %.5f %.6f %.6f %.6f %.6f", originRaw.x, originRaw.y,
|
||||
originRaw.z, q[0], q[1], q[2], q[3]);
|
||||
SendTo(out, "ft_pointer", msg);
|
||||
} else if (active && anchored && hmd.bPoseIsValid) {
|
||||
const bool dragging = leftHeld || tnow < dropHoldUntil;
|
||||
if (!dragging) tiltYaw = tiltPitch = 0; // drop finished: back to plain pointing
|
||||
const Vec3 dir = Direction(yaw, pitch);
|
||||
// Nearest visible overlay along a ray (frozen while dragging).
|
||||
struct Hit {
|
||||
double along = 1e9;
|
||||
std::string key;
|
||||
bool scene = false;
|
||||
Vec3 point, normal;
|
||||
};
|
||||
auto nearest = [&](Vec3 from, Vec3 d) {
|
||||
}
|
||||
|
||||
// Nearest visible overlay along a ray (frozen while dragging).
|
||||
Pointer::Hit Pointer::Nearest(Vec3 from, Vec3 d) {
|
||||
Hit h;
|
||||
for (const auto &[key, handle] : handles) {
|
||||
if (!visible[key]) continue;
|
||||
@@ -1982,9 +2137,18 @@ int Pointer::Run() {
|
||||
}
|
||||
}
|
||||
return h;
|
||||
};
|
||||
}
|
||||
|
||||
// Where the cursor is this frame: on a panel, a scene-graph plane, or a panel's edge, or out in free
|
||||
// space.
|
||||
Pointer::Spot Pointer::FindCursor(const Frame &frame) {
|
||||
const auto tnow = frame.tnow;
|
||||
const Vec3 &eye = frame.eye;
|
||||
const bool dragging = leftHeld || tnow < dropHoldUntil;
|
||||
if (!dragging) tiltYaw = tiltPitch = 0; // drop finished: back to plain pointing
|
||||
const Vec3 dir = Direction(yaw, pitch);
|
||||
Hit first;
|
||||
if (!dragging) first = nearest(anchor, dir);
|
||||
if (!dragging) first = Nearest(anchor, dir);
|
||||
double best = first.along;
|
||||
std::string bestKey = first.key;
|
||||
bool bestScene = first.scene;
|
||||
@@ -2045,7 +2209,7 @@ int Pointer::Run() {
|
||||
// covers from where you are now (panels close together in view, at different
|
||||
// depths). Anything in front of the point on the eye's line of sight wins.
|
||||
const double toPoint = std::sqrt(Dot(point - eye, point - eye));
|
||||
const Hit front = nearest(eye, Normalize(point - eye));
|
||||
const Hit front = Nearest(eye, Normalize(point - eye));
|
||||
if (front.along < toPoint - 0.02) {
|
||||
occluded = true;
|
||||
bestKey = front.key, bestScene = front.scene;
|
||||
@@ -2060,6 +2224,18 @@ int Pointer::Run() {
|
||||
}
|
||||
const bool onScene = !dragging && ((bestScene && best < 1e8) || onEdge);
|
||||
const bool onPanel = dragging || (best < 1e8 && !onScene);
|
||||
Spot s;
|
||||
s.dragging = dragging, s.dir = dir, s.point = point, s.best = best, s.distance = distance;
|
||||
s.onEdge = onEdge, s.occluded = occluded, s.onScene = onScene, s.onPanel = onPanel;
|
||||
return s;
|
||||
}
|
||||
|
||||
// Our dot where the cursor is (see "Looks" and "SteamVR Settings" at the top).
|
||||
void Pointer::DrawDot(const Frame &frame, const Spot &spot) {
|
||||
const auto tnow = frame.tnow;
|
||||
const Vec3 &eye = frame.eye;
|
||||
const bool dragging = spot.dragging, onScene = spot.onScene, onPanel = spot.onPanel;
|
||||
const Vec3 dir = spot.dir, point = spot.point;
|
||||
const Vec3 sight = Normalize(point - eye);
|
||||
if (!dragging) {
|
||||
// SteamVR Settings (see the top): the dashboard's main panel is hidden and its
|
||||
@@ -2130,7 +2306,16 @@ int Pointer::Run() {
|
||||
overlay->ShowOverlay(show);
|
||||
overlay->HideOverlay(hide);
|
||||
}
|
||||
}
|
||||
|
||||
void Pointer::SendRay(const Frame &frame, const Spot &spot) {
|
||||
const vr::TrackedDevicePose_t &hmd = frame.Hmd();
|
||||
const auto tnow = frame.tnow;
|
||||
const Vec3 &eye = frame.eye;
|
||||
const vr::TrackedDevicePose_t &hmdRaw = frame.hmdRaw;
|
||||
const bool dragging = spot.dragging, occluded = spot.occluded, onEdge = spot.onEdge, onScene = spot.onScene;
|
||||
const double best = spot.best, distance = spot.distance;
|
||||
const Vec3 point = spot.point;
|
||||
// Controller ray: from the eye, aimed at the cursor point, converted from the
|
||||
// standing universe to raw tracking space via the HMD's pose in both.
|
||||
const Vec3 aimStanding = Normalize(point - eye);
|
||||
@@ -2178,10 +2363,12 @@ int Pointer::Run() {
|
||||
std::snprintf(msg, sizeof msg, "pose %.5f %.5f %.5f %.4f %.4f", eyeRaw.x, eyeRaw.y, eyeRaw.z, ayaw, apitch);
|
||||
SendTo(out, "ft_pointer", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A held-back press (see the top): held still long enough, it's a real press (a drag);
|
||||
// released, it's a click where the pointer is now (this frame's pose has gone out).
|
||||
// A held-back press (see the top): held still long enough, it's a real press (a drag);
|
||||
// released, it's a click where the pointer is now (this frame's pose has gone out).
|
||||
void Pointer::HeldBackPress(const Frame &frame) {
|
||||
const auto tnow = frame.tnow;
|
||||
if (!active) aimHeld = aimRight = clickPress = aimHand = confirmLesson = false; // released meanwhile: nothing to click
|
||||
if (aimHeld && !aimHand && nudgeMoved < 0.2 && tnow - aimSince >= std::chrono::duration<double>(cfg.gazeHold)) {
|
||||
aimHeld = false;
|
||||
@@ -2190,8 +2377,14 @@ int Pointer::Run() {
|
||||
aimRight = false;
|
||||
PressLeft();
|
||||
}
|
||||
// A keyboard click held still for POINTER_GAZE_HOLD: a real press, then the head drags
|
||||
// (from where it is now).
|
||||
}
|
||||
|
||||
// A keyboard click held still for POINTER_GAZE_HOLD: a real press, then the head drags
|
||||
// (from where it is now).
|
||||
void Pointer::KeyboardClickHold(const Frame &frame) {
|
||||
const vr::TrackedDevicePose_t &hmd = frame.Hmd();
|
||||
const auto &hm = hmd.mDeviceToAbsoluteTracking.m;
|
||||
const auto tnow = frame.tnow;
|
||||
if (aimHeld && aimHand && hold.promote && !hold.engaged &&
|
||||
tnow - aimSince >= std::chrono::duration<double>(cfg.gazeHold)) {
|
||||
aimHeld = aimHand = false;
|
||||
@@ -2207,6 +2400,11 @@ int Pointer::Run() {
|
||||
if (debug) std::printf("hold key %s: pressed (held still)\n", hold.right ? "right" : "left");
|
||||
if (debug) std::fflush(stdout);
|
||||
}
|
||||
}
|
||||
|
||||
// The click a held-back press ends in: the press now, the release 40 ms later.
|
||||
void Pointer::Click(const Frame &frame) {
|
||||
const auto tnow = frame.tnow;
|
||||
if (clickPress) {
|
||||
clickPress = false;
|
||||
PressLeft();
|
||||
@@ -2216,13 +2414,19 @@ int Pointer::Run() {
|
||||
clickRelease = false;
|
||||
ReleaseLeft();
|
||||
}
|
||||
}
|
||||
|
||||
// Frame controller buttons (vrbuttons.h), to the relay.
|
||||
void Pointer::PollControllerButtons() {
|
||||
controllerButtons.Poll(
|
||||
[&](const char *button, bool down) {
|
||||
SendTo(out, "frametop_relay", std::string("vrbtn ") + button + (down ? " 1" : " 0"));
|
||||
},
|
||||
inGame);
|
||||
}
|
||||
|
||||
// SteamVR quits: shut down (true: main returns).
|
||||
bool Pointer::SteamVRQuit() {
|
||||
vr::VREvent_t ev;
|
||||
while (sys->PollNextEvent(&ev, sizeof ev)) {
|
||||
if (ev.eventType == vr::VREvent_Quit) {
|
||||
@@ -2230,15 +2434,45 @@ int Pointer::Run() {
|
||||
|
||||
overlays.Stop();
|
||||
vr::VR_Shutdown();
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(8));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int main() {
|
||||
Pointer p;
|
||||
p.Init();
|
||||
return p.Run();
|
||||
// Each frame: the sections in this order (Pointer's members above).
|
||||
while (true) {
|
||||
p.HeadsetOff();
|
||||
p.GazeAwake();
|
||||
p.RelayCommands();
|
||||
Frame frame;
|
||||
p.ReadFrame(frame);
|
||||
p.ClaimPulse(frame);
|
||||
p.PauseOverlayList();
|
||||
p.LaserMode();
|
||||
p.HandRole(frame);
|
||||
p.LastUsedWins(frame);
|
||||
p.Recenter(frame);
|
||||
p.GazeMode(frame);
|
||||
p.Hands(frame);
|
||||
p.GazePrecisionButtons(frame);
|
||||
p.KeyboardClicks(frame);
|
||||
p.CalPanelOpened();
|
||||
p.HeadSteer(frame);
|
||||
p.PinchesAndGrips(frame);
|
||||
p.HandsStopped(frame);
|
||||
p.MarkHeld(frame);
|
||||
p.HeadFollow(frame);
|
||||
p.SlowWork();
|
||||
p.Cursor(frame);
|
||||
p.HeldBackPress(frame);
|
||||
p.KeyboardClickHold(frame);
|
||||
p.Click(frame);
|
||||
p.PollControllerButtons();
|
||||
if (p.SteamVRQuit()) return 0;
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(8));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user