HomeGuidesChangelogDiscussions
Hire a ReactVision Expert 🛠️Launch Studio 🖥️Sponsor on GitHub ❤️Log In
Guides

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) on ViroARSceneNavigator. Not available with provider="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>

Cheap enough to call about once a second. Each measure includes the threshold it is judged against:

FieldMeaning
keyframes / minKeyframes / meetsKeyframesKeyframe coverage
viewpointPairs / minViewpointPairs / meetsViewpointPairsBaseline viewpoint pairs
cameraSpreadMeters / minSpreadMeters / meetsSpreadCamera path spread

Use them to show UI like "keyframes 31/40" instead of a spinner.

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>

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() over onWorldMeshUpdated. The onWorldMeshUpdated prop 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>

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.

❗️

onCloudAnchorStateChange was removed in 3.0.1. It was never wired. Use hostCloudAnchor / resolveCloudAnchor results, getCloudAnchorStatus(), or rvGetCloudAnchor(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

MistakeWhat happens now
finishScan() without startScan()Fails with "No scan in progress" (does not host unrelated buffer data)
Snapshot before mesh has accumulatedNamed 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. Enabled now waits for real horizontal accuracy (~15 m), not merely a provider object existing.
  • isLocationAccuracyReduced() — true when the OS only granted approximate location (Precise Location off / coarse-only). In that state Localizing never converges — prompt the user.
  • setGeospatialModeEnabled(enabled) — start geospatial tracking before polling the above.

Platform support

CapabilityiOSAndroidQuest / visionOS / Web
startScan / finishScan✅✅❌
Mesh snapshot / attach✅ (LiDAR)✅ (ARCore depth)❌
GPS accuracy gating✅✅❌

Related