Guest User

Untitled

a guest
Aug 24th, 2026
38
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. (function () {
  2.   const xr = navigator.xr;
  3.   if (!xr) return console.error("[xr-hands] no navigator.xr");
  4.  
  5.   const nativeRequest = Function.prototype.call.bind(XRSystem.prototype.requestSession);
  6.   const nativeGetPose = Function.prototype.call.bind(XRFrame.prototype.getPose);
  7.   const nativeGetViewerPose = XRFrame.prototype.getViewerPose
  8.     ? Function.prototype.call.bind(XRFrame.prototype.getViewerPose)
  9.     : null;
  10.  
  11.   const PINCH_ON = 0.022;
  12.   const lastPose = new WeakMap();
  13.   const lastAim = new WeakMap();
  14.   const pressed = new WeakSet();
  15.   let lockedHand = null;
  16.  
  17.   function isSpace(x) {
  18.     return !!(x && typeof XRSpace !== "undefined" && x instanceof XRSpace);
  19.   }
  20.  
  21.   function joint(hand, name) {
  22.     try { return hand && hand.get && hand.get(name); } catch { return null; }
  23.   }
  24.  
  25.   function pos(frame, space, ref) {
  26.     if (!frame || !isSpace(space) || !isSpace(ref)) return null;
  27.     try {
  28.       const p = nativeGetPose(frame, space, ref);
  29.       return p && p.transform && p.transform.position;
  30.     } catch { return null; }
  31.   }
  32.  
  33.   function quatFromForward(fx, fy, fz) {
  34.     const zlen = Math.hypot(fx, fy, fz) || 1;
  35.     const zx = -fx / zlen, zy = -fy / zlen, zz = -fz / zlen;
  36.     let ux = 0, uy = 1, uz = 0;
  37.     let rx = uy * zz - uz * zy, ry = uz * zx - ux * zz, rz = ux * zy - uy * zx;
  38.     if (Math.hypot(rx, ry, rz) < 1e-6) {
  39.       ux = 1; uy = 0; uz = 0;
  40.       rx = uy * zz - uz * zy; ry = uz * zx - ux * zz; rz = ux * zy - uy * zx;
  41.     }
  42.     const rl = Math.hypot(rx, ry, rz) || 1;
  43.     rx /= rl; ry /= rl; rz /= rl;
  44.     ux = zy * rz - zz * ry; uy = zz * rx - zx * rz; uz = zx * ry - zy * rx;
  45.     const m00 = rx, m01 = ux, m02 = zx;
  46.     const m10 = ry, m11 = uy, m12 = zy;
  47.     const m20 = rz, m21 = uz, m22 = zz;
  48.     const t = m00 + m11 + m22;
  49.     let x, y, z, w;
  50.     if (t > 0) {
  51.       const s = Math.sqrt(t + 1) * 2;
  52.       w = 0.25 * s; x = (m21 - m12) / s; y = (m02 - m20) / s; z = (m10 - m01) / s;
  53.     } else if (m00 > m11 && m00 > m22) {
  54.       const s = Math.sqrt(1 + m00 - m11 - m22) * 2;
  55.       w = (m21 - m12) / s; x = 0.25 * s; y = (m01 + m10) / s; z = (m02 + m20) / s;
  56.     } else if (m11 > m22) {
  57.       const s = Math.sqrt(1 + m11 - m00 - m22) * 2;
  58.       w = (m02 - m20) / s; x = (m01 + m10) / s; y = 0.25 * s; z = (m12 + m21) / s;
  59.     } else {
  60.       const s = Math.sqrt(1 + m22 - m00 - m11) * 2;
  61.       w = (m10 - m01) / s; x = (m02 + m20) / s; y = (m12 + m21) / s; z = 0.25 * s;
  62.     }
  63.     return { x, y, z, w };
  64.   }
  65.  
  66.   function makePose(origin, target) {
  67.     try {
  68.       return {
  69.         transform: new XRRigidTransform(
  70.           { x: origin.x, y: origin.y, z: origin.z },
  71.           quatFromForward(target.x - origin.x, target.y - origin.y, target.z - origin.z)
  72.         ),
  73.         emulatedPosition: false
  74.       };
  75.     } catch {
  76.       return null;
  77.     }
  78.   }
  79.  
  80.   function computeAim(frame, src, ref) {
  81.     const hand = src && src.hand;
  82.     if (!hand || !frame || !ref) return null;
  83.     const origin =
  84.       pos(frame, joint(hand, "index-finger-metacarpal"), ref) ||
  85.       pos(frame, joint(hand, "index-finger-phalanx-proximal"), ref) ||
  86.       pos(frame, joint(hand, "wrist"), ref);
  87.     const tip =
  88.       pos(frame, joint(hand, "index-finger-tip"), ref) ||
  89.       pos(frame, joint(hand, "index-finger-phalanx-distal"), ref);
  90.     if (!origin || !tip) return null;
  91.     const dx = tip.x - origin.x, dy = tip.y - origin.y, dz = tip.z - origin.z;
  92.     const len = Math.hypot(dx, dy, dz) || 1;
  93.     return makePose({
  94.       x: origin.x + (dx / len) * 0.03,
  95.       y: origin.y + (dy / len) * 0.03,
  96.       z: origin.z + (dz / len) * 0.03
  97.     }, tip);
  98.   }
  99.  
  100.   function snapshot(frame, src, ref) {
  101.     if (!frame || !src || !isSpace(ref)) return;
  102.     for (const space of [src.targetRaySpace, src.gripSpace]) {
  103.       if (!isSpace(space)) continue;
  104.       try {
  105.         const pose = nativeGetPose(frame, space, ref);
  106.         if (pose) {
  107.           lastPose.set(src, pose);
  108.           return;
  109.         }
  110.       } catch {}
  111.     }
  112.     const aim = computeAim(frame, src, ref);
  113.     if (aim) {
  114.       lastPose.set(src, aim);
  115.       lastAim.set(src, aim);
  116.     }
  117.   }
  118.  
  119.   XRFrame.prototype.getPose = function (space, baseSpace) {
  120.     const session = this.session;
  121.     const ref = (isSpace(baseSpace) && baseSpace) || (session && session.__ref) || null;
  122.     if (isSpace(space) && isSpace(ref)) {
  123.       try {
  124.         const pose = nativeGetPose(this, space, ref);
  125.         if (pose) return pose;
  126.       } catch {}
  127.     }
  128.     if (session) {
  129.       for (const src of session.inputSources || []) {
  130.         if (space && (space === src.targetRaySpace || space === src.gripSpace)) {
  131.           return lastPose.get(src) || lastAim.get(src) || null;
  132.         }
  133.       }
  134.       if (session.__poseHand) {
  135.         return lastPose.get(session.__poseHand) || lastAim.get(session.__poseHand) || null;
  136.       }
  137.     }
  138.     return null;
  139.   };
  140.  
  141.   if (nativeGetViewerPose) {
  142.     XRFrame.prototype.getViewerPose = function (ref) {
  143.       try {
  144.         const pose = nativeGetViewerPose(this, ref);
  145.         if (pose) return pose;
  146.       } catch {}
  147.       const live = this.session && this.session.__frame;
  148.       if (live && live !== this) {
  149.         try { return nativeGetViewerPose(live, ref); } catch {}
  150.       }
  151.       return null;
  152.     };
  153.   }
  154.  
  155.   function setButton(src, down) {
  156.     try {
  157.       const btn = src.gamepad && src.gamepad.buttons && src.gamepad.buttons[0];
  158.       if (!btn) return;
  159.       Object.defineProperty(btn, "pressed", { configurable: true, get: () => !!down });
  160.       Object.defineProperty(btn, "touched", { configurable: true, get: () => !!down });
  161.       Object.defineProperty(btn, "value", { configurable: true, get: () => (down ? 1 : 0) });
  162.     } catch {}
  163.   }
  164.  
  165.   function handsOf(session) {
  166.     return [...session.inputSources].filter((s) => s.hand && s.targetRayMode === "tracked-pointer");
  167.   }
  168.  
  169.   function pinchDistance(frame, src, ref) {
  170.     const a = pos(frame, joint(src.hand, "thumb-tip"), ref);
  171.     const b = pos(frame, joint(src.hand, "index-finger-tip"), ref);
  172.     if (!a || !b) return Infinity;
  173.     return Math.hypot(a.x - b.x, a.y - b.y, a.z - b.z);
  174.   }
  175.  
  176.   function pickHand(session, frame, ref, transient) {
  177.     const all = handsOf(session);
  178.     if (lockedHand && [...session.inputSources].includes(lockedHand)) return lockedHand;
  179.     if (transient && (transient.handedness === "left" || transient.handedness === "right")) {
  180.       const match = all.find((s) => s.handedness === transient.handedness);
  181.       if (match) return match;
  182.     }
  183.     let best = null, bestD = PINCH_ON;
  184.     for (const src of all) {
  185.       const d = pinchDistance(frame, src, ref);
  186.       if (d < bestD) {
  187.         bestD = d;
  188.         best = src;
  189.       }
  190.     }
  191.     return best || all[0] || null;
  192.   }
  193.  
  194.   function fire(session, type, hand) {
  195.     if (!hand) return;
  196.     session.__poseHand = hand;
  197.     setButton(hand, type !== "selectend");
  198.     if (type !== "selectend") pressed.add(hand);
  199.     else pressed.delete(hand);
  200.     const frame = session.__frame;
  201.     if (!frame) return;
  202.     try {
  203.       const ev = new XRInputSourceEvent(type, { frame, inputSource: hand });
  204.       ev.__fromPinch = true;
  205.       session.dispatchEvent(ev);
  206.     } catch (err) {
  207.       console.warn("[xr-hands] dispatch failed", type, err);
  208.     }
  209.   }
  210.  
  211.   function attach(session) {
  212.     if (!session || session.__xrHandsBridge3) return session;
  213.     session.__xrHandsBridge3 = true;
  214.     session.requestReferenceSpace("local-floor")
  215.       .catch(() => session.requestReferenceSpace("local"))
  216.       .catch(() => session.requestReferenceSpace("viewer"))
  217.       .then((space) => { session.__ref = space; })
  218.       .catch(() => {});
  219.  
  220.     const origRAF = session.requestAnimationFrame.bind(session);
  221.     session.requestAnimationFrame = function (cb) {
  222.       return origRAF((time, frame) => {
  223.         session.__frame = frame;
  224.         const ref = session.__ref;
  225.         for (const hand of handsOf(session)) {
  226.           snapshot(frame, hand, ref);
  227.           setButton(hand, pressed.has(hand));
  228.         }
  229.         cb(time, frame);
  230.       });
  231.     };
  232.  
  233.     const on = (type) => (event) => {
  234.       if (event.__fromPinch) return;
  235.       const src = event.inputSource;
  236.       if (!src || src.targetRayMode !== "transient-pointer") return;
  237.       const frame = session.__frame || event.frame;
  238.       const hand = pickHand(session, frame, session.__ref, src);
  239.       if (type === "selectstart") lockedHand = hand;
  240.       if (type === "selectend") {
  241.         const endHand = lockedHand || hand;
  242.         console.info("[xr-hands]", type, endHand && endHand.handedness);
  243.         fire(session, type, endHand);
  244.         lockedHand = null;
  245.         session.__poseHand = null;
  246.         return;
  247.       }
  248.       console.info("[xr-hands]", type, hand && hand.handedness);
  249.       fire(session, type, lockedHand || hand);
  250.     };
  251.  
  252.     session.addEventListener("selectstart", on("selectstart"));
  253.     session.addEventListener("select", on("select"));
  254.     session.addEventListener("selectend", on("selectend"));
  255.     console.info("[xr-hands] bridged v3 — one hand, beam-aligned pose");
  256.     return session;
  257.   }
  258.  
  259.   xr.requestSession = async function (mode, options) {
  260.     const src = options && typeof options === "object" ? options : {};
  261.     const drop = new Set([
  262.       "anchors", "hit-test", "depth-sensing", "dom-overlay",
  263.       "light-estimation", "camera-access", "plane-detection", "mesh-detection"
  264.     ]);
  265.     const filter = (list) => (Array.isArray(list) ? list.filter((f) => !drop.has(f)) : []);
  266.     const next = {
  267.       requiredFeatures: filter(src.requiredFeatures),
  268.       optionalFeatures: filter(src.optionalFeatures)
  269.     };
  270.     if (!next.optionalFeatures.includes("local-floor")) next.optionalFeatures.push("local-floor");
  271.     if (!next.optionalFeatures.includes("hand-tracking")) next.optionalFeatures.push("hand-tracking");
  272.     const useMode = mode === "immersive-ar" ? "immersive-vr" : mode;
  273.     console.info("[xr-hands] requestSession", useMode, next);
  274.     return attach(await nativeRequest(this, useMode, next));
  275.   };
  276.  
  277.   async function launch() {
  278.     const api = window.ZeroXRLoading;
  279.     if (!api || typeof api.startSession !== "function") throw new Error("ZeroXRLoading.startSession missing");
  280.     const ctrl = api.getActiveController && api.getActiveController();
  281.     if (ctrl && !ctrl.ended) {
  282.       try { ctrl.end(); } catch {}
  283.       await new Promise((r) => setTimeout(r, 500));
  284.     }
  285.     const started = await api.startSession({ mode: "immersive-vr" });
  286.     if (started && started.session) attach(started.session);
  287.     return started;
  288.   }
  289.  
  290.   const existing = window.ZeroXRLoading && window.ZeroXRLoading.getActiveController && window.ZeroXRLoading.getActiveController();
  291.   if (existing && existing.session && !existing.ended) attach(existing.session);
  292.  
  293.   const onClick = async () => {
  294.     document.removeEventListener("click", onClick, true);
  295.     try { console.info("[xr-hands] started", await launch()); }
  296.     catch (err) { console.error("[xr-hands] start failed", err); }
  297.   };
  298.   document.addEventListener("click", onClick, true);
  299.   window.__xrStartHands = launch;
  300.   console.info("[xr-hands] v3 ready. Pinch-click the page, point one beam, pinch.");
  301. })();
Advertisement
Add Comment
Please, Sign In to add comment