World understanding
xb.world exposes enabled physical-world evidence. Configure each branch before
initialization, then convert fresh evidence into explicit application state.
Every branch can be unsupported, warming up, empty, stale, or failed.
Choose the evidence
| Evidence | Enable | Typical reaction |
|---|---|---|
| Coarse floors, walls, and tables | options.enablePlaneDetection() | One-time placement or surface-specific state |
| Platform room geometry | options.world.enableMeshDetection() | Visualization, collision, or room-aware behavior |
| Live per-pixel distance and depth mesh | options.enableDepth() | Reticle projection, occlusion, or collision |
| Named physical objects | options.enableObjectDetection() plus backend prerequisites | Labels, grounded actions, or scene state |
| Human body joints | options.enableHumanDetection() | Pose-aware application behavior |
| Face landmarks and expressions | options.enableFaceDetection() | Avatar or expression behavior |
| Person segmentation mask | options.enableSegmentation() | Camera effects or person/background separation |
Use Depth and Occlusion for depth details. Use Placement scripts to distinguish one-time room placement from continuous follow, face, orbit, and visibility behavior.
Plane detection
const options = new xb.Options();
options.enablePlaneDetection();
await xb.init(options);
Planes are coarse semantic surfaces supplied by WebXR or the simulator. The plane collection can remain empty on unsupported devices.
For one-time placement, let XR Blocks inspect available planes and meshes:
const placed = await xb.world.placeOnHorizontalSurface(model, {seconds: 15});
if (!placed) showPlacementUnavailable();
The result is a boolean. Treat false as a normal no-suitable-surface result.
The helper places once; it does not keep the object anchored or facing the
viewer. Read Placement scripts for continuous behavior and
manipulation rebasing.
Scene mesh detection
const options = new xb.Options();
options.world.enableMeshDetection();
options.world.meshes.showDebugVisualizations = true;
await xb.init(options);
Scene meshes are platform-reconstructed room geometry. Read current meshes from
xb.world.meshes.xrMeshToThreeMesh. The map can be empty and native support is
experimental. The simulator can inject its environment meshes through the same
runtime branch.
When Rapier is configured, enabled scene meshes can create environment colliders. Mesh detection does not enable physics by itself.
Object detection
Object detection captures the environment camera, finds named items in 2D, and uses depth to ground them in world space. Choose the backend explicitly.
Backends
Under the hood, object detection uses pluggable backends selected via options.world.objects.backend:
GeminiDetectorBackend: Queries Gemini models using visual camera frames (requires AI API keys, see the Generative AI guide).MediaPipeDetectorBackend: Runs local MediaPipe object detection model.
Gemini setup:
const options = new xb.Options();
options.enableAI();
options.enableCamera('environment');
options.enableDepth();
options.enableObjectDetection();
options.world.objects.backendConfig.activeBackend = 'gemini';
This branch sends captured image data to Google and requires prototype or production credentials as described in Generative AI.
On-device MediaPipe setup:
const options = new xb.Options();
options.enableCamera('environment');
options.enableDepth();
options.enableObjectDetection();
options.world.objects.backendConfig.activeBackend = 'mediapipe';
Run one detection:
const objects = await xb.world.objects.runDetection();
for (const object of objects) {
console.log(object.name, object.position);
}
Concurrent calls share the active detection. A new run clears previous
detected-object scene nodes before publishing current results. Treat [] as a
normal warm-up, empty, unavailable, or failed result and update application
state accordingly.
For continuous ownership, pair the same client object:
const client = {};
xb.world.objects.start(client);
// Later:
xb.world.objects.stop(client);
Use options.world.objects.pollingIntervalMs to control cadence. The desktop
simulator can supply deterministic object ground truth when
options.world.objects.simulatorOverride = true and its environment defines
detectable objects.
Human pose detection
enableHumanDetection() declares camera permission and enables the environment
camera and depth prerequisites:
const options = new xb.Options();
options.enableHumanDetection();
await xb.init(options);
const poses = await xb.world.humans.runDetection();
const leftWrist = poses[0]?.getJointPosition(xb.PoseJointName.LeftWrist);
Human detection uses on-device MediaPipe and projects supported joints into
world space with depth. Guard empty results and missing joints. Use
start(client) and stop(client) for continuous ownership.
Inference runs in a web worker by default so a detection pass does not stall
the render loop. Workers have no DOM canvas for MediaPipe to draw on, so the
worker uses the CPU delegate; set
options.world.humans.backendConfig.mediapipe.useWorker = false to run on the
main thread with the GPU delegate instead. When the browser has no worker
support the SDK falls back to the main thread on its own.
Face landmark detection
enableFaceDetection() declares camera permission and enables camera and depth:
const options = new xb.Options();
options.enableFaceDetection();
await xb.init(options);
const faces = await xb.world.faces.runDetection();
const nose = faces[0]?.getLandmarkPosition(xb.FaceLandmarkName.NoseTip);
const jawOpen = faces[0]?.getBlendshape('jawOpen');
Each result can contain world-space landmarks, optional blendshapes, and an
optional facial transformation. Guard every optional output. Use
start(client) and stop(client) for continuous ownership.
Semantic segmentation
Segmentation is an on-device 2D camera operation and does not require depth:
const options = new xb.Options();
options.enableSegmentation();
await xb.init(options);
const mask = await xb.world.segmentation.runSegmentation();
if (mask) console.log(mask.width, mask.height, mask.data);
Read latestMask for the newest completed inference. null is a normal
not-ready or unavailable state. Category values are exported as
xb.SegmentCategory.
Freshness, permission, and cleanup
- Declare camera and other permissions before entering immersive XR.
- Lock explicit requests when duplicate work has no meaning.
- Use the matching
start(client)andstop(client)pair for continuous detectors. - Clear old visible results when a new detection is empty or stale.
- Distinguish synthetic simulator evidence from native device evidence.
- Stop application-owned polling and media resources in
dispose().
Seeing real people on the desktop simulator
Face detection, human pose detection, and segmentation all look for people, but
enableFaceDetection(), enableHumanDetection(), and enableSegmentation()
each turn on the environment camera, which is right on a headset: you are
detecting the people in front of you.
On desktop that same setting hands MediaPipe the simulator's rendered room, which never contains a person, so detection silently returns nothing. Ask for the user-facing camera instead:
options.enableHumanDetection();
// After the helper, which calls enableCamera() internally and would otherwise
// overwrite this.
options.enableCamera('user');
Do not reach for options.simulator.deviceCamera.enabled = false here. It does
route frames from the real webcam, but it also stops the simulator camera being
registered, and on desktop that is what supplies the camera pose. Without a
pose, getCameraParametersSnapshot() returns null and detection is skipped
before MediaPipe runs, leaving you with a working camera and no results.
enableCamera('user') keeps the pose and changes only the stream.
Neither affects a real headset, where the simulator never starts.
For working samples, see samples/depthmesh/main.js,