Desktop simulator
The simulator runs XR Blocks applications in a normal desktop browser. It uses the same scripts, targeting, selection, and world APIs as a real XR session. It does not emulate the browser WebXR API.
Start the simulator
Show the simulator option in the XR button:
const options = new xb.Options();
options.xrButton.showEnterSimulatorButton = true;
await xb.init(options);
Autostart it in code or by URL:
options.formFactor = 'desktop';
http://127.0.0.1:8080/templates/14_simulator_setup/?formFactor=desktop
The simulator runtime is loaded only when startup is requested. On a desktop autostart
path, await xb.init(options) waits for that load. Applications can also call:
await xb.init(options);
const simulator = await xb.core.startSimulator();
xb.core.simulator is undefined before startup completes. Use onSimulatorStarted() when
a script must react to simulator startup.
Browser interface
The settings, instructions, and hand-pose panels register automatically when Lit is available. No simulator add-on import is required. CDN applications need these mappings:
{
"imports": {
"lit": "https://esm.sh/lit@3.3.1",
"lit/": "https://esm.sh/lit@3.3.1/"
}
}
Disable panels before initialization when an application needs a clean canvas:
options.simulator.simulatorSettingsPanel.enabled = false;
options.simulator.instructions.enabled = false;
options.simulator.handPosePanel.enabled = false;
Control modes
XR Blocks supplies five simulator modes:
SimulatorMode.USER: user movement and mouse selection.SimulatorMode.POSE: navigation plus virtual hand-pose controls.SimulatorMode.CONTROLLER: drive an active virtual hand or controller.SimulatorMode.POINTER_LOCK: first-person pointer-lock navigation.SimulatorMode.EDITOR: simulator scene editing.
options.simulator.defaultMode = xb.SimulatorMode.CONTROLLER;
options.simulator.modeToggle.enabled = true;
When mode toggling is enabled, the configured toggle key cycles through
options.simulator.modeToggle.toggleOrder.
Automation preset
Use the shared preset for automated or external browser runs:
const options = new xb.Options().enableAutomationMode({
hideSimulatorUi: true,
defaultMode: xb.SimulatorMode.POSE,
enableHands: true,
enableCamera: true,
});
await xb.init(options);
?xrAutomation=1 applies the default preset. ?debug=1 separately exposes window.xb
and the initialization promise window.xbReady.
Scene environments
Each environment points to one JSON manifest:
options.simulator.environments = [
{name: 'Evaluation Room', manifestPath: './evaluation-room.json'},
];
options.simulator.activeEnvironmentIndex = 0;
{
"scenePath": "./room.glb",
"scenePlanesPath": "./room-planes.json",
"navMeshPath": "./room-navmesh.glb",
"position": [0, 0, 0],
"locations": {
"wall-primary": {
"description": "Clear wall point for ray interaction.",
"position": [0, 1.4, -2]
}
},
"objects": [
{
"id": "chair",
"assetPath": "./chair.glb",
"position": [0.5, 0, -1.5],
"physics": "fixed",
"detectObject": true,
"label": "chair"
}
]
}
Use scenePath or videoPath, not both. Paths can be manifest-relative, root-relative,
or absolute URLs. Object physics can be false, fixed, or dynamic.
Load another environment at runtime with:
await xb.core.simulator.setEnvironment('Ad Hoc Room', './ad-hoc-room.json');
Read the optional named world-space locations from the active environment:
const locations = xb.core.simulator.getLocations();
const wall = locations['wall-primary'];
console.log(wall.description, wall.position);
Navmesh constraints
options.simulator.navMesh.enabled = true;
options.simulator.navMesh.showDebugVisualizations = true;
The navmesh constrains and grounds the simulated user. It does not constrain hands or
controllers. CDN applications that enable it need the three-pathfinding mapping shown in
rollup.config.js and templates/14_simulator_setup/index.html.
Runtime physical objects
const [chair] = await xb.core.simulator.objects.addObjects([
{
assetPath: './chair.glb',
physics: 'fixed',
detectObject: true,
label: 'chair',
},
]);
xb.core.simulator.objects.get([chair.id]);
xb.core.simulator.objects.removeObjects([chair.id]);
xb.core.simulator.objects.clear();
Use simulator ground truth through the normal world detector API:
options.enableObjectDetection();
options.world.objects.simulatorOverride = true;
const objects = await xb.world.objects.runDetection();
The override applies only in the simulator. Real XR sessions use the configured detector backend.
Hand reach and physical hands
options.simulator.reachDistance.enabled = true;
options.simulator.reachDistance.radius = 0.75;
options.simulator.reachAngle.enabled = true;
options.simulator.reachAngle.angle = Math.PI;
options.simulator.handPhysics.enabled = true;
Physical hands require Rapier. leftHandOrigin and rightHandOrigin are camera-local
shoulder anchors for reach limits and the physics tether. Set
options.simulator.physics.enabled = false to disable simulator-owned collision without
disabling the application's Rapier world.
See templates/14_simulator_setup, demos/sim_hand_poses, and the API reference for
SimulatorOptions.