mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 01:00:13 +02:00
keyboard.vr.touchTyping (off by default): a key is pressed when a controller's tip, where SteamVR's laser starts (/pose/tip: the device pose times the render model's "tip" component), passes through it from the front; released when pulled back 1 cm, re-armed 5 mm in front. Both hands, also at once. Keys go through the keyboard's own HandleTouchStart / HandleTouchEnd, like a laser press (Backspace repeats while held). Options depth (cm) and haptics. The geometry lives in a reusable controller bridge (vr-keyboard-controllers, internal option): a systemui patch reads the keyboard's pose with SteamVR's SGQueryService (an empty transform of ours in the keyboard's mount) and both controllers' poses, and streams per hand the tip, the laser's hit on the keyboard and the trigger (~90 Hz while relevant) through the vr-keyboard-controllers-relay service to __sfuiControllers in Steam's SharedJSContext. Tests: flake checks vr-keyboard-controllers and vr-keyboard-touch.
67 lines
3.0 KiB
JavaScript
67 lines
3.0 KiB
JavaScript
// tracker.js: when a controller's tip touches a key (vr-keyboard-touch.nix;
|
|
// argument of keyboard-patch.js, tested by tests/tracker.test.mjs).
|
|
// Evaluates to { VERSION, DEFAULTS, createTracker }.
|
|
//
|
|
// createTracker(opts): one hand. update(sample, tMs) with the tip relative
|
|
// to the keyboard ({ u, v, d } of vr-keyboard-controllers' geometry.js:
|
|
// keyboard widths from the top left corner, m behind the surface; null: no
|
|
// pose / keyboard unusable) returns null or an event { phase, u, v }:
|
|
// down the tip passed through the surface (at `depth` behind it) from
|
|
// the front between two samples, at u, v (interpolated) inside the
|
|
// keyboard, not faster than maxStep per sample, minIntervalMs
|
|
// after the last release;
|
|
// up it came back out: release m back from its deepest point, or in
|
|
// front of the surface by arm; u, v where it is now;
|
|
// cancel the sample was null while down.
|
|
// A press needs the tip in front by arm (re-armed) since the last one, so
|
|
// tracking jitter at the surface can't repeat it, and the tip coming from
|
|
// behind or around the keyboard never presses.
|
|
(() => {
|
|
const VERSION = 1;
|
|
const DEFAULTS = {
|
|
depth: 0, // m behind the drawn surface where a touch registers
|
|
arm: 0.005, // m in front of it to re-arm
|
|
release: 0.01, // m back from the deepest point to release
|
|
maxV: 0.5, // keyboard height bound (widths; the page is ~0.33)
|
|
maxStep: 0.08, // m per sample; more is a tracking jump, not a touch
|
|
minIntervalMs: 60, // release -> next press
|
|
};
|
|
|
|
function createTracker(opts = {}) {
|
|
const O = { ...DEFAULTS, ...opts };
|
|
let prev = null, armed = false, pressed = false, deepest = 0, releasedAt = -Infinity;
|
|
const inside = (u, v) => u >= 0 && u <= 1 && v >= 0 && v <= O.maxV;
|
|
function update(s, t) {
|
|
if (!s) {
|
|
prev = null; armed = false;
|
|
if (!pressed) return null;
|
|
pressed = false; releasedAt = t;
|
|
return { phase: 'cancel' };
|
|
}
|
|
const d = s.d - O.depth;
|
|
let ev = null;
|
|
if (pressed) {
|
|
deepest = Math.max(deepest, d);
|
|
if (d < deepest - O.release || d < -O.arm) {
|
|
pressed = false; armed = false; releasedAt = t;
|
|
ev = { phase: 'up', u: s.u, v: s.v };
|
|
}
|
|
} else if (armed && prev && prev.d < 0 && d >= 0) {
|
|
armed = false;
|
|
const f = prev.d / (prev.d - d); // where the segment meets the surface
|
|
const u = prev.u + f * (s.u - prev.u), v = prev.v + f * (s.v - prev.v);
|
|
if (inside(u, v) && d - prev.d <= O.maxStep && t - releasedAt >= O.minIntervalMs) {
|
|
pressed = true; deepest = d;
|
|
ev = { phase: 'down', u, v };
|
|
}
|
|
} else if (d < -O.arm) armed = true;
|
|
else if (d >= 0) armed = false;
|
|
prev = { u: s.u, v: s.v, d };
|
|
return ev;
|
|
}
|
|
return { update, get pressed() { return pressed; } };
|
|
}
|
|
|
|
return { VERSION, DEFAULTS, createTracker };
|
|
})()
|