Files
mitch030504--Wiicompiled_VR…/runtime/include/vr/eye_gaze.h
T
Claude b1a8b034d9 Centre foveation on the player's gaze on headsets with eye tracking
With [vr] eye_tracked_foveation (on by default on the Steam Frame, off
elsewhere) the runtime asks for XR_EXT_eye_gaze_interaction. When the
system reports an eye tracker, OpenXRInput binds the gaze pose and
locates it for each packet's display time, in the space the eye views
are located in; vr/eye_gaze.h turns it into tangents of each eye's own
view, which AuroraStereoFrame now carries (appended, after the existing
prefix).

Aurora centres the eye's fragment density map on the gaze snapped to a
cell of two map texels (about 3 degrees). Each eye keeps up to 32 maps,
one per cell looked at, so a glance back reuses its map; a new map is
bound once its upload completes, and until then the eye keeps the map
it had. Without a tracked gaze (a blink, no tracker, the setting off)
foveation centres on the forward direction exactly as before: the
forward maps are byte-identical.

Also logs every extension the OpenXR runtime offers at startup, so the
first Steam Frame session shows what SteamVR's Android runtime has.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019HBRGKTE1GnN2ah8gcZKr3
2026-10-04 08:44:30 +00:00

61 lines
2.2 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <cmath>
// Eye-tracked foveation ([vr] eye_tracked_foveation): where the player looks, as each eye's image
// measures it. Kept free of OpenXR types so it can be checked headlessly (tests/vr_eye_gaze_tests.cpp).
//
// Conventions are OpenXR's: right-handed, +Y up, and a pose looks down its -Z axis.
namespace mkw::vr::eye_gaze {
struct Quaternion {
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
float w = 1.0f;
};
// Tangents of an eye's view, x right and y up, as its frustum (XrFovf) measures them.
struct Tangents {
float x = 0.0f;
float y = 0.0f;
bool valid = false;
};
// Beyond this angle from an eye's forward direction a gaze is no point of its image (cos 80 deg).
inline constexpr float kMinForwardCosine = 0.17364818f;
// The gaze pose's look direction in one eye's view, from both orientations in the same space. The
// eyes' views can be canted outwards, so each eye gets its own tangents.
inline Tangents InEye(Quaternion gaze, Quaternion eye) noexcept {
const auto normalized = [](Quaternion q) {
const float length = std::sqrt(q.x * q.x + q.y * q.y + q.z * q.z + q.w * q.w);
if (!(length > 1.0e-6f)) {
return Quaternion{};
}
return Quaternion{q.x / length, q.y / length, q.z / length, q.w / length};
};
// q * v * conjugate(q).
const auto rotate = [](const Quaternion& q, float vx, float vy, float vz, float out[3]) {
const float tx = 2.0f * (q.y * vz - q.z * vy);
const float ty = 2.0f * (q.z * vx - q.x * vz);
const float tz = 2.0f * (q.x * vy - q.y * vx);
out[0] = vx + q.w * tx + (q.y * tz - q.z * ty);
out[1] = vy + q.w * ty + (q.z * tx - q.x * tz);
out[2] = vz + q.w * tz + (q.x * ty - q.y * tx);
};
gaze = normalized(gaze);
eye = normalized(eye);
float look[3];
rotate(gaze, 0.0f, 0.0f, -1.0f, look);
float seen[3];
rotate(Quaternion{-eye.x, -eye.y, -eye.z, eye.w}, look[0], look[1], look[2], seen);
if (!(-seen[2] > kMinForwardCosine)) {
return {};
}
return {seen[0] / -seen[2], seen[1] / -seen[2], true};
}
} // namespace mkw::vr::eye_gaze