XR Origin & Spaces
Updated: Sep 4, 2026
XROrigin is the persistent local player origin. IWSDK creates it for every World, including browser-only worlds, and parents world.camera below it.
Move world.player for first-person movement. For an orbit, editor, product, cinematic, or third-person camera, keep the player at the origin and move the camera locally.
head contains the viewer pose.raySpaces.left/right contain target-ray poses for pointing.gripSpaces.left/right contain device grip poses for held tools.indexTipSpaces.left/right contain fingertip poses used by touch pointers.secondaryRaySpaces.left/right and secondaryGripSpaces.left/right track non-primary input sources.
The head, primary ray and grip spaces, and index-tip spaces are attached below the origin by default. Secondary spaces are updated but are not attached for rendering.
const origin = world.input.xr.xrOrigin;
origin.raySpaces.right.add(laserSight);
origin.gripSpaces.left.add(heldTool);
Each tracked space contains position and orientation. The local negative Z axis of a target-ray space is its pointing direction.
A grip space uses the device profile’s physical holding pose. It is not required to point in the same direction as the target ray. Parent a held model to the grip space, then adjust the model’s local position and orientation so its handle matches the controller.
heldTool.position.set(0, -0.02, 0.05);
heldTool.rotation.set(0, Math.PI / 2, 0);
origin.gripSpaces.left.add(heldTool);
When an input source has no gripSpace, IWSDK mirrors its ray pose into the matching grip space.
During each XR input update, IWSDK:
- Assigns each input source to a primary or secondary side.
- Copies target-ray and grip poses into the corresponding groups.
- Updates primary hand index-tip spaces from joint poses, or copies the
matching primary ray pose when a controller is active.
- Applies the ray-pose fallback when a grip pose is absent.
- Updates the head from the viewer pose.
- Updates the origin’s world matrices before pointer processing.
The origin lives in world space. Its tracked children receive poses relative to the XR reference space.
const localPoint = cursorWorld.clone();
world.input.xr.xrOrigin.worldToLocal(localPoint);
world.camera.position is local to world.player. Use getWorldPosition() when an algorithm needs the viewer’s world-space position.
const viewerWorld = world.camera.getWorldPosition(new Vector3());
Place viewer-relative UI safely
Do not parent normal menus or persistent status panels directly to the head. Direct children inherit every viewer movement and cannot be looked away from.
Reserve direct head attachment for a small transient marker that requires exact view alignment.
const marker = createSmallAimMarker();
marker.position.set(0, 0, -1);
origin.head.add(marker);
Use world-space placement for normal UI. Use Follower when compact global UI must remain discoverable without copying every head pose.