Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- (function () {
- const xr = navigator.xr;
- if (!xr) return console.error("[xr-hands] no navigator.xr");
- const nativeRequest = Function.prototype.call.bind(XRSystem.prototype.requestSession);
- const nativeGetPose = Function.prototype.call.bind(XRFrame.prototype.getPose);
- const nativeGetViewerPose = XRFrame.prototype.getViewerPose
- ? Function.prototype.call.bind(XRFrame.prototype.getViewerPose)
- : null;
- const PINCH_ON = 0.022;
- const lastPose = new WeakMap();
- const lastAim = new WeakMap();
- const pressed = new WeakSet();
- let lockedHand = null;
- function isSpace(x) {
- return !!(x && typeof XRSpace !== "undefined" && x instanceof XRSpace);
- }
- function joint(hand, name) {
- try { return hand && hand.get && hand.get(name); } catch { return null; }
- }
- function pos(frame, space, ref) {
- if (!frame || !isSpace(space) || !isSpace(ref)) return null;
- try {
- const p = nativeGetPose(frame, space, ref);
- return p && p.transform && p.transform.position;
- } catch { return null; }
- }
- function quatFromForward(fx, fy, fz) {
- const zlen = Math.hypot(fx, fy, fz) || 1;
- const zx = -fx / zlen, zy = -fy / zlen, zz = -fz / zlen;
- let ux = 0, uy = 1, uz = 0;
- let rx = uy * zz - uz * zy, ry = uz * zx - ux * zz, rz = ux * zy - uy * zx;
- if (Math.hypot(rx, ry, rz) < 1e-6) {
- ux = 1; uy = 0; uz = 0;
- rx = uy * zz - uz * zy; ry = uz * zx - ux * zz; rz = ux * zy - uy * zx;
- }
- const rl = Math.hypot(rx, ry, rz) || 1;
- rx /= rl; ry /= rl; rz /= rl;
- ux = zy * rz - zz * ry; uy = zz * rx - zx * rz; uz = zx * ry - zy * rx;
- const m00 = rx, m01 = ux, m02 = zx;
- const m10 = ry, m11 = uy, m12 = zy;
- const m20 = rz, m21 = uz, m22 = zz;
- const t = m00 + m11 + m22;
- let x, y, z, w;
- if (t > 0) {
- const s = Math.sqrt(t + 1) * 2;
- w = 0.25 * s; x = (m21 - m12) / s; y = (m02 - m20) / s; z = (m10 - m01) / s;
- } else if (m00 > m11 && m00 > m22) {
- const s = Math.sqrt(1 + m00 - m11 - m22) * 2;
- w = (m21 - m12) / s; x = 0.25 * s; y = (m01 + m10) / s; z = (m02 + m20) / s;
- } else if (m11 > m22) {
- const s = Math.sqrt(1 + m11 - m00 - m22) * 2;
- w = (m02 - m20) / s; x = (m01 + m10) / s; y = 0.25 * s; z = (m12 + m21) / s;
- } else {
- const s = Math.sqrt(1 + m22 - m00 - m11) * 2;
- w = (m10 - m01) / s; x = (m02 + m20) / s; y = (m12 + m21) / s; z = 0.25 * s;
- }
- return { x, y, z, w };
- }
- function makePose(origin, target) {
- try {
- return {
- transform: new XRRigidTransform(
- { x: origin.x, y: origin.y, z: origin.z },
- quatFromForward(target.x - origin.x, target.y - origin.y, target.z - origin.z)
- ),
- emulatedPosition: false
- };
- } catch {
- return null;
- }
- }
- function computeAim(frame, src, ref) {
- const hand = src && src.hand;
- if (!hand || !frame || !ref) return null;
- const origin =
- pos(frame, joint(hand, "index-finger-metacarpal"), ref) ||
- pos(frame, joint(hand, "index-finger-phalanx-proximal"), ref) ||
- pos(frame, joint(hand, "wrist"), ref);
- const tip =
- pos(frame, joint(hand, "index-finger-tip"), ref) ||
- pos(frame, joint(hand, "index-finger-phalanx-distal"), ref);
- if (!origin || !tip) return null;
- const dx = tip.x - origin.x, dy = tip.y - origin.y, dz = tip.z - origin.z;
- const len = Math.hypot(dx, dy, dz) || 1;
- return makePose({
- x: origin.x + (dx / len) * 0.03,
- y: origin.y + (dy / len) * 0.03,
- z: origin.z + (dz / len) * 0.03
- }, tip);
- }
- function snapshot(frame, src, ref) {
- if (!frame || !src || !isSpace(ref)) return;
- for (const space of [src.targetRaySpace, src.gripSpace]) {
- if (!isSpace(space)) continue;
- try {
- const pose = nativeGetPose(frame, space, ref);
- if (pose) {
- lastPose.set(src, pose);
- return;
- }
- } catch {}
- }
- const aim = computeAim(frame, src, ref);
- if (aim) {
- lastPose.set(src, aim);
- lastAim.set(src, aim);
- }
- }
- XRFrame.prototype.getPose = function (space, baseSpace) {
- const session = this.session;
- const ref = (isSpace(baseSpace) && baseSpace) || (session && session.__ref) || null;
- if (isSpace(space) && isSpace(ref)) {
- try {
- const pose = nativeGetPose(this, space, ref);
- if (pose) return pose;
- } catch {}
- }
- if (session) {
- for (const src of session.inputSources || []) {
- if (space && (space === src.targetRaySpace || space === src.gripSpace)) {
- return lastPose.get(src) || lastAim.get(src) || null;
- }
- }
- if (session.__poseHand) {
- return lastPose.get(session.__poseHand) || lastAim.get(session.__poseHand) || null;
- }
- }
- return null;
- };
- if (nativeGetViewerPose) {
- XRFrame.prototype.getViewerPose = function (ref) {
- try {
- const pose = nativeGetViewerPose(this, ref);
- if (pose) return pose;
- } catch {}
- const live = this.session && this.session.__frame;
- if (live && live !== this) {
- try { return nativeGetViewerPose(live, ref); } catch {}
- }
- return null;
- };
- }
- function setButton(src, down) {
- try {
- const btn = src.gamepad && src.gamepad.buttons && src.gamepad.buttons[0];
- if (!btn) return;
- Object.defineProperty(btn, "pressed", { configurable: true, get: () => !!down });
- Object.defineProperty(btn, "touched", { configurable: true, get: () => !!down });
- Object.defineProperty(btn, "value", { configurable: true, get: () => (down ? 1 : 0) });
- } catch {}
- }
- function handsOf(session) {
- return [...session.inputSources].filter((s) => s.hand && s.targetRayMode === "tracked-pointer");
- }
- function pinchDistance(frame, src, ref) {
- const a = pos(frame, joint(src.hand, "thumb-tip"), ref);
- const b = pos(frame, joint(src.hand, "index-finger-tip"), ref);
- if (!a || !b) return Infinity;
- return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
- }
- function pickHand(session, frame, ref, transient) {
- const all = handsOf(session);
- if (lockedHand && [...session.inputSources].includes(lockedHand)) return lockedHand;
- if (transient && (transient.handedness === "left" || transient.handedness === "right")) {
- const match = all.find((s) => s.handedness === transient.handedness);
- if (match) return match;
- }
- let best = null, bestD = PINCH_ON;
- for (const src of all) {
- const d = pinchDistance(frame, src, ref);
- if (d < bestD) {
- bestD = d;
- best = src;
- }
- }
- return best || all[0] || null;
- }
- function fire(session, type, hand) {
- if (!hand) return;
- session.__poseHand = hand;
- setButton(hand, type !== "selectend");
- if (type !== "selectend") pressed.add(hand);
- else pressed.delete(hand);
- const frame = session.__frame;
- if (!frame) return;
- try {
- const ev = new XRInputSourceEvent(type, { frame, inputSource: hand });
- ev.__fromPinch = true;
- session.dispatchEvent(ev);
- } catch (err) {
- console.warn("[xr-hands] dispatch failed", type, err);
- }
- }
- function attach(session) {
- if (!session || session.__xrHandsBridge3) return session;
- session.__xrHandsBridge3 = true;
- session.requestReferenceSpace("local-floor")
- .catch(() => session.requestReferenceSpace("local"))
- .catch(() => session.requestReferenceSpace("viewer"))
- .then((space) => { session.__ref = space; })
- .catch(() => {});
- const origRAF = session.requestAnimationFrame.bind(session);
- session.requestAnimationFrame = function (cb) {
- return origRAF((time, frame) => {
- session.__frame = frame;
- const ref = session.__ref;
- for (const hand of handsOf(session)) {
- snapshot(frame, hand, ref);
- setButton(hand, pressed.has(hand));
- }
- cb(time, frame);
- });
- };
- const on = (type) => (event) => {
- if (event.__fromPinch) return;
- const src = event.inputSource;
- if (!src || src.targetRayMode !== "transient-pointer") return;
- const frame = session.__frame || event.frame;
- const hand = pickHand(session, frame, session.__ref, src);
- if (type === "selectstart") lockedHand = hand;
- if (type === "selectend") {
- const endHand = lockedHand || hand;
- console.info("[xr-hands]", type, endHand && endHand.handedness);
- fire(session, type, endHand);
- lockedHand = null;
- session.__poseHand = null;
- return;
- }
- console.info("[xr-hands]", type, hand && hand.handedness);
- fire(session, type, lockedHand || hand);
- };
- session.addEventListener("selectstart", on("selectstart"));
- session.addEventListener("select", on("select"));
- session.addEventListener("selectend", on("selectend"));
- console.info("[xr-hands] bridged v3 — one hand, beam-aligned pose");
- return session;
- }
- xr.requestSession = async function (mode, options) {
- const src = options && typeof options === "object" ? options : {};
- const drop = new Set([
- "anchors", "hit-test", "depth-sensing", "dom-overlay",
- "light-estimation", "camera-access", "plane-detection", "mesh-detection"
- ]);
- const filter = (list) => (Array.isArray(list) ? list.filter((f) => !drop.has(f)) : []);
- const next = {
- requiredFeatures: filter(src.requiredFeatures),
- optionalFeatures: filter(src.optionalFeatures)
- };
- if (!next.optionalFeatures.includes("local-floor")) next.optionalFeatures.push("local-floor");
- if (!next.optionalFeatures.includes("hand-tracking")) next.optionalFeatures.push("hand-tracking");
- const useMode = mode === "immersive-ar" ? "immersive-vr" : mode;
- console.info("[xr-hands] requestSession", useMode, next);
- return attach(await nativeRequest(this, useMode, next));
- };
- async function launch() {
- const api = window.ZeroXRLoading;
- if (!api || typeof api.startSession !== "function") throw new Error("ZeroXRLoading.startSession missing");
- const ctrl = api.getActiveController && api.getActiveController();
- if (ctrl && !ctrl.ended) {
- try { ctrl.end(); } catch {}
- await new Promise((r) => setTimeout(r, 500));
- }
- const started = await api.startSession({ mode: "immersive-vr" });
- if (started && started.session) attach(started.session);
- return started;
- }
- const existing = window.ZeroXRLoading && window.ZeroXRLoading.getActiveController && window.ZeroXRLoading.getActiveController();
- if (existing && existing.session && !existing.ended) attach(existing.session);
- const onClick = async () => {
- document.removeEventListener("click", onClick, true);
- try { console.info("[xr-hands] started", await launch()); }
- catch (err) { console.error("[xr-hands] start failed", err); }
- };
- document.addEventListener("click", onClick, true);
- window.__xrStartHands = launch;
- console.info("[xr-hands] v3 ready. Pinch-click the page, point one beam, pinch.");
- })();
Advertisement
Add Comment
Please, Sign In to add comment