update — keep it strictly render‑oriented (posing meshes, materials, effects). Launching projectiles, grabbing objects, etc. should live elsewhere.onClick, onPointerDown, onPointerMove, …) rather than polling buttons.'xr-standard-squeeze') instead of device‑specific indices.xrOrigin.raySpaces.left/right to attach tools aligned with the pointing ray.xrOrigin.gripSpaces.left/right to attach held items (flashlights, gadgets).Visuals (render only)
↓
Pointers (primary interactions across modalities)
↓
Stateful Gamepad (advanced input & axes)
↓
XR Origin Spaces (where to attach tools)
StatefulGamepad helper that exposes edge‑triggered button events, 2D axes magnitudes, and per‑direction transitions.MultiPointer that aggregates built‑in ray + grab pointers using @pmndrs/pointer-events.import { PerspectiveCamera, Scene, WebGLRenderer } from 'three';
import { XRInputManager } from '@iwsdk/xr-input';
const scene = new Scene();
const camera = new PerspectiveCamera(60, innerWidth / innerHeight, 0.01, 100);
const renderer = new WebGLRenderer({ antialias: true });
renderer.xr.enabled = true;
// 1) Create input manager
const xrInput = new XRInputManager({ scene, camera });
scene.add(xrInput.xrOrigin); // parent the rig
// 2) Optional: react to the primary adapter (left/right)
xrInput.visualAdapters.left.subscribe((adapter) => {
console.log('Left primary is now', adapter?.constructor.name);
});
// 3) In your frame loop
renderer.setAnimationLoop((timeMs) => {
const time = timeMs / 1000;
const delta = renderer.clock.getDelta();
xrInput.update(renderer.xr, delta, time);
renderer.render(scene, camera);
});
const button = new Mesh( new BoxGeometry(0.12, 0.02, 0.12), new MeshStandardMaterial(), ); (button as any).onClick = () => spawnProjectile(); // works with controllers & hands scene.add(button);
const pad = xrInput.gamepads.right;
if (pad?.getButtonDown('xr-standard-trigger')) spawnProjectile();
if (pad?.getAxesEnteringLeft('xr-standard-thumbstick')) snapTurn(-30);
const gizmo = new Object3D(); gizmo.position.set(0, -0.02, 0.05); xrInput.xrOrigin.gripSpaces.left.add(gizmo);
XRSession → XRFrame → update poses
│
├─ XROrigin (head, ray L/R, grip L/R)
│
├─ Visual Adapters
│ • Controller (GLTF, animated via input profile)
│ • Hand (skinned, outline, pinch helper)
│
└─ MultiPointer (ray + grab via @pmndrs/pointer-events)
• select → ray pointer
• squeeze → grab pointer
• edge events from StatefulGamepad
inputSource.gamepad.