VPS — persistent room-scale AR
VPS (Visual Positioning System) turns a scanned room into a persistent coordinate frame you can localise back into later or from another device. Optionally attach a world mesh for occlusion and physics.
This is ReactVision's v1 "island" model: self-contained anchors discoverable near where they were hosted, not a continuous city-scale map. It reuses the cloud-anchor host/resolve pipeline.
Requires
provider="reactvision"(the default) onViroARSceneNavigator. Not available withprovider="arcore".
Available in ViroReact 3.0.0 and later.
Quick start
// 1. Scan — no pre-placed anchor needed
sceneNavigator.startScan();
// ...user walks the room...
const { success, cloudAnchorId, locationTransform, diagnostics } =
await sceneNavigator.finishScan(30 /* ttlDays */);
// 2. Optional: snapshot world mesh in the scan's location frame
if (success) {
const { filePath } = await sceneNavigator.snapshotWorldMeshToFile(locationTransform);
// upload filePath via rvUploadAsset() → rvAttachAssetToCloudAnchor(cloudAnchorId, ...)
}
// 3. Later / other device: resolve + reload mesh
const resolved = await sceneNavigator.resolveCloudAnchor(cloudAnchorId);
await sceneNavigator.loadWorldMeshFromFile(localMeshPath, resolved.anchor.resolvedTransform);
All of these methods live on the scene's sceneNavigator (the same object as hostCloudAnchor), not on an external ref alone.
Scan observability (new in 3.0.0)
A scan used to look identical to a device doing nothing. Poll these while someone walks the room:
getScanStatus(): Promise<ViroScanStatus>
getScanStatus(): Promise<ViroScanStatus>Cheap enough to call about once a second. Each measure includes the threshold it is judged against:
| Field | Meaning |
|---|---|
keyframes / minKeyframes / meetsKeyframes | Keyframe coverage |
viewpointPairs / minViewpointPairs / meetsViewpointPairs | Baseline viewpoint pairs |
cameraSpreadMeters / minSpreadMeters / meetsSpread | Camera path spread |
Use them to show UI like "keyframes 31/40" instead of a spinner.
getScanDiagnostics(): Promise<ViroScanDiagnostics>
getScanDiagnostics(): Promise<ViroScanDiagnostics>Measurements behind the last finishScan(), pass or fail. A failed finishScan() also attaches them as diagnostics so you can name which number fell short.
getWorldMeshStats(): Promise<ViroWorldMeshStatsResult>
getWorldMeshStats(): Promise<ViroWorldMeshStatsResult>Whether a mesh exists yet and how big it is. available: false is ordinary — reason separates "capture is off" from "keep looking around".
Prefer polling
getWorldMeshStats()overonWorldMeshUpdated. TheonWorldMeshUpdatedprop has never fired (the native bridge does not forward mesh updates). It remains for compile compatibility and is deprecated for practical use.
Cloud-anchor progress
getCloudAnchorStatus(): Promise<ViroCloudAnchorStatus>
getCloudAnchorStatus(): Promise<ViroCloudAnchorStatus>type ViroCloudAnchorStatus = {
active: boolean; // false when nothing is resolving
progress: number; // 0–1
message: string; // downloading vs searching vs waiting on a second match
};
Poll during resolveCloudAnchor(). The promise is silent until the resolve ends; message is what lets you tell the user whether to keep standing still.
onCloudAnchorStateChangewas removed in 3.0.1. It was never wired. UsehostCloudAnchor/resolveCloudAnchorresults,getCloudAnchorStatus(), orrvGetCloudAnchor(anchorId)instead.
World mesh prop
<ViroARSceneNavigator worldMeshEnabled={true} ... />
Required for snapshots and for loaded meshes to render/collide. There is no setWorldMeshEnabled() method — drive the prop from React state.
Call order
| Mistake | What happens now |
|---|---|
finishScan() without startScan() | Fails with "No scan in progress" (does not host unrelated buffer data) |
| Snapshot before mesh has accumulated | Named precondition error (capture off vs empty mesh vs wrong scene type, etc.) |
Mixing locationTransform (host) with resolvedTransform (resolve) | Wrong placement — they are parallel concepts from different calls |
GPS accuracy helpers
When using geospatial discovery alongside VPS:
getEarthTrackingState()—Stopped|Localizing|Enabled|Paused.Enablednow waits for real horizontal accuracy (~15 m), not merely a provider object existing.isLocationAccuracyReduced()—truewhen the OS only granted approximate location (Precise Location off / coarse-only). In that stateLocalizingnever converges — prompt the user.setGeospatialModeEnabled(enabled)— start geospatial tracking before polling the above.
Platform support
| Capability | iOS | Android | Quest / visionOS / Web |
|---|---|---|---|
startScan / finishScan | ✅ | ✅ | ❌ |
| Mesh snapshot / attach | ✅ (LiDAR) | ✅ (ARCore depth) | ❌ |
| GPS accuracy gating | ✅ | ✅ | ❌ |
Related
- Co-Location — multi-device shared frames on top of cloud / Meta / visionOS anchors
- Cloud Anchors — host/resolve without a full room scan
- ViroARSceneNavigator — prop and method reference
Updated about 3 hours ago