Develop

Chapter 6: Built-in Interactions

Updated: Sep 4, 2026
IWSDK can handle browser pointers, XR controller rays, hand input, grabbing, and locomotion. Enable the systems that your app needs, then add interaction components to scene entities.

Enable input features

Configure grabbing and locomotion in iwsdk.config.json:
{
  "world": {
    "xr": {
      "mode": "vr",
      "features": {
        "handTracking": true
      }
    },
    "input": {
      "canvasPointerEvents": true
    },
    "features": {
      "grabbing": true,
      "locomotion": true
    }
  }
}
Canvas pointer events are enabled by default. They let browser clicks and XR rays use the same pointer-event path.

Make an entity selectable

Add RayInteractable for ray or browser-pointer input:
import {
  BoxGeometry,
  Mesh,
  MeshStandardMaterial,
  RayInteractable,
} from '@iwsdk/core';

const cube = new Mesh(
  new BoxGeometry(0.4, 0.4, 0.4),
  new MeshStandardMaterial({ color: 0x3355ff }),
);

world.createTransformEntity(cube).addComponent(RayInteractable);
InputSystem adds the transient Hovered and Pressed tags as pointer state changes. Query those tags from an application system.
Use PokeInteractable for near touch or poke input.

Add grabbing

Grabbing must be enabled in the project manifest. Add one of these components to an entity that can receive pointer hits:
  • OneHandGrabbable: Moves or rotates an object with one grip.
  • TwoHandsGrabbable: Supports two-handed manipulation.
  • DistanceGrabbable: Moves, rotates, or scales an object from a distance.
For example:
import {
  DistanceGrabbable,
  MovementMode,
  RayInteractable,
} from '@iwsdk/core';

world
  .createTransformEntity(mesh)
  .addComponent(RayInteractable)
  .addComponent(DistanceGrabbable, {
    movementMode: MovementMode.MoveFromTarget,
  });

Add gaze-enhanced interaction

On Meta VR Glasses, gaze chooses a target and a hand pinch selects it. Merge the following XR feature settings into the root iwsdk.config.json:
{
  "$schema": "./node_modules/@iwsdk/core/dist/schemas/iwsdk-project.v1.schema.json",
  "version": "iwsdk.project.v1",
  "scene": "./public/scenes/main.iwsdk.scene.json",
  "world": {
    "xr": {
      "mode": "vr",
      "features": {
        "handTracking": { "required": true },
        "gazeTracking": true
      }
    }
  }
}
gazeTracking: true requests gaze tracking as an optional session feature. Use gazeTracking: { "required": true } only when the immersive session must fail if the feature is unavailable. If the feature is optional and the runtime does not provide a usable gaze pose, IWSDK falls back to a ray that follows the head pose.
The deprecated eyeTracking key is an alias for gazeTracking. Use gazeTracking in new projects.

Add an interaction target

Gaze uses existing RayInteractable targets. It emits the ordinary ray-pointer lifecycle and drives the same Hovered and Pressed ECS tags as hand rays, so no gaze-specific component or event handler is required.
import { RayInteractable } from '@iwsdk/core';

target.addComponent(RayInteractable);
When grabbing is enabled, DistanceGrabbable is also a gaze target. Looking at the object and pinching starts a distance grab. By default, the captured pointer then follows the pinching hand until the pinch is released.
Gaze yields while a hand ray, grab pointer, or touch pointer is actively hovering or selecting. This keeps direct hand interaction from competing with gaze targeting.

Tune gaze targeting

The world.features.gaze object overrides gaze behavior; it does not enable gaze by itself. world.xr.features.gazeTracking is still required.
FieldDefaultBehavior
suppressWhenHandPointerActive
true
Yield while a hand pointer is active.
filterMinCutoff
1.5
Set the minimum cutoff for gaze smoothing.
filterBeta
0.05
Reduce smoothing during faster eye movement.
dwellWindowSeconds
0.15
Set the target-consensus window in seconds. Set 0 to use each frame’s winner.
coneAngle
5
Set the selection-cone half-angle in degrees.
maxRayLength
30
Set the maximum target distance in meters.
pointerTransformFollowsHand
true
Make a captured interaction follow the pinching hand’s ray.
logDiagnostics
true
Log gaze-source diagnostics and show the fallback reticle state.
Check whether gaze is tracked
A white reticle indicates a tracked gaze pose. An amber reticle indicates the head-pose fallback. In the browser console, messages beginning with `[iwsdk][gaze]` distinguish a session feature that was not granted, a missing gaze source, an invalid pose, and a gaze source mapped to the head pose.
For a complete Meta VR Glasses setup, see Get started with Meta VR Glasses using IWSDK.

Add locomotion surfaces

When locomotion is enabled, mark walkable scene objects with LocomotionEnvironment:
import {
  EnvironmentType,
  LocomotionEnvironment,
} from '@iwsdk/core';

world
  .createTransformEntity(environmentMesh)
  .addComponent(LocomotionEnvironment, {
    type: EnvironmentType.STATIC,
  });
The left thumbstick moves the player. The right thumbstick turns the player. The locomotion system handles collision and ground contact against registered environments.

Enable browser locomotion

A browser-first app can bind keyboard and browser gamepad input to locomotion actions:
{
  "world": {
    "xr": false,
    "features": {
      "locomotion": {
        "browserControls": true
      }
    }
  }
}
The default bindings include WASD, arrow keys, Space, and standard browser gamepads. IWSDK does not supply a browser camera-look mode. Your app remains responsible for pointer lock, orbit, touch-look, or third-person camera behavior.

Troubleshooting

An entity does not receive pointer events

Symptom: A ray or browser pointer reaches the object, but its state does not change.
Solution: Add RayInteractable and confirm that the object’s mesh can be raycast. Also confirm that input is enabled for the selected runtime mode.

An object cannot be grabbed

Symptom: The object receives pointer events but does not move.
Solution: Enable grabbing and add a grab component. Confirm that the entity receives the pointer action expected by that grab component.

The player cannot move

Symptom: Locomotion input is active, but the player remains in place.
Solution: Add LocomotionEnvironment to the walkable geometry. Use EnvironmentType.STATIC for fixed surfaces.