ReactVision Spatial, the services that
make a place persistent and shareable
The spatial services layer behind ViroReact. Cloud anchors, room-scale scans, geospatial anchors, a live co-location channel, and replicated application state, written in C++ with no external vision libraries and no Google dependencies. It ships inside the ViroCore engine, so there is nothing to install on its own.
Host a spot and relocalise it across devices and sessions, from camera imagery and the AR point cloud. Persistent, shared AR content in one place.
Walk a room and host it as an anchor that carries its own coordinate frame, with no tapped anchor to be relative to. The VPS half of the library.
Anchors placed at latitude, longitude, and altitude, and the maths that projects GPS into the AR frame.
Live peer poses between devices that share a frame, at 20 Hz. Lossy by design: the next pose supersedes the last.
Ordered, conflict-resolved, server-authoritative application state for those devices. Saved when a room empties and reloaded when it reopens.
v1.2.0, proprietary, shipping inside ViroReact. Five independent subsystems: an app can use one and ignore the rest.
What Spatial is
ReactVision Spatial is the spatial services layer behind ViroReact: everything that makes a place persistent and shareable between devices. It provides cloud anchors, room-scale scans, geospatial anchors, a co-location channel, and replicated application state, written in C++ with no external vision libraries and no Google dependencies, and it reaches apps through the ViroCore engine as a prebuilt library.
Nothing here is called directly from an application. The API an app sees is on ViroReact's ViroARSceneNavigator, and this library sits behind it: host and resolve a cloud anchor, start and finish a scan, create and find geospatial anchors, join a co-location room. Both live channels are served by ReactVision's hosted relay, so an app passes an API key and a project id and nothing else.
How a cloud anchor is hosted and resolved
Two halves. Hosting turns what a device saw into a compact, relocalisable description of a spot. Resolving finds that spot again from a different device, in a different session, with a different world origin.
Collect keyframes continuously
From the moment a provider exists, camera frames are captured into a ring buffer on a background thread. A frame is kept only when the camera has moved at least 2 cm or turned 5 degrees, so the buffer holds distinct viewpoints rather than duplicates.
Describe each frame
Each keyframe is subsampled, equalised for lighting, and run through ReactVision's own SIFT implementation: a difference-of-Gaussians pyramid and 128-byte descriptors, up to 1,000 keypoints per frame on the host side.
Triangulate and upload
On host, keyframe pairs with enough baseline are matched and triangulated into anchor-local 3D points, deduplicated on a 2 cm grid, gated for quality, then serialised into ReactVision's binary anchor format and uploaded to the platform.
Match the live frame
The current camera image is described the same way and matched against the stored descriptors. The heavy work runs on a detached background thread so the render loop stays smooth.
Solve the pose
P3P RANSAC estimates the camera pose from the 2D to 3D matches, a descriptor-quality gate rejects geometrically lucky but wrong matches, and Gauss-Newton refinement tightens the result. Scan-built anchors are refined over yaw and translation only, because their frame is gravity-aligned by construction.
Vote before accepting
A pose is accepted when three of the last ten solves agree within 45 cm. The reported pose is the cluster member with the most inliers, so one outlier between two good solves no longer stalls a resolve.
The result is a transform from the anchor's local frame into the current device's world, which is what lets content placed by one person appear in the same place for the next.
Room-scale scans and the coverage gate
A scan is a cloud anchor without a tapped anchor to be relative to. Walk the room, and the library computes a location frame from the scan's own geometry: origin at the centroid of the triangulated points, orientation yaw-only against gravity. Content placed against a scan travels in that frame, never in world coordinates.
| Measure | Minimum | Why |
|---|---|---|
| Triangulated points | 40 | Below this there is not enough structure to match against later. |
| Spread of those points | 1.0 m | A scan of one corner localises only from that corner. |
| Viewpoint pairs at least 3 cm apart | 5 | Parallax makes depth observable. Rotating in place gives none. |
A scan reports its own progress while it happens. An app can poll the keyframe count, camera spread, and usable viewpoint pairs, each beside the threshold it is judged against, so a rejected scan can say which number was short. These thresholds were set for device bring-up and will be tuned from measured scans.
Co-location and replicated state
A resolved anchor gives every device in a space the same origin. Two channels carry what they say to each other about it, on two sockets, because poses and application state cannot share one set of rules.
Where each peer is, at 20 Hz
Frame-native data only. Poses originate in C++ at frame rate and never cross the JavaScript bridge. The channel is lossy by design: the next pose supersedes the last, so nothing is queued and nothing needs replaying.
- Room
- The cloud anchor id. Peers that resolved the same anchor share a room; there is no separate session concept.
- Coordinates
- Always the anchor's location frame, never world coordinates. Each AR session starts its world wherever tracking began, so a world pose means nothing to another device.
- Rate
- Poses go out at 20 Hz and receivers interpolate. A pose inside the interval is dropped, not queued, so there is no backlog after a reconnect.
- Reconnection
- Automatic, with capped backoff. Losing the socket loses the peers for a moment, never the frame.
What the room agrees on
Application state originates in JavaScript and must not be lossy, so it is server-authoritative and totally ordered. A server already sits in the path, and only a server can express a refusal, which ownership requires. State is saved when a room empties and reloaded when it reopens.
- Authority
- Clients propose operations; the server accepts or rejects each one, assigns the room's next sequence number, and broadcasts. Ordering is identical for everyone.
- Ownership
- Ownership gates mutation, so the peer dragging an object is the only one that can move it.
- Concurrency
- An expected version opts into optimistic concurrency, with the current value returned on refusal. Unowned entities are last-writer-wins.
- Departure
- A peer that leaves releases everything it held, so a crash mid-drag does not lock an object for the life of the room.
Platform support
The anchor pipeline needs ARKit or ARCore camera frames. The two live channels need only a network socket, which is why they reach further than the pipeline does.
| Platform | Minimum | AR framework | Coverage |
|---|---|---|---|
| Android | API 24+ | ARCore 1.51+ | Everything. |
| iOS | 15.0+ | ARKit 3.0+ | Everything. |
| visionOS | 1.0+ | None | Channel and replication only. The AR pipeline needs camera frames, which visionOS grants only under an enterprise entitlement. |
| Meta Quest | n/a | n/a | Not a target for this library. Quest co-location uses Meta's own spatial anchors, inside ViroCore. |
There is no desktop build: a host has no camera frames, and the build says so rather than producing something that cannot work.
Why we built our own
Most AR stacks lease their anchors from a platform vendor. ReactVision owns this layer for the same reason it owns the engine: so one codebase reaches every device, and so the whole stack can run where the customer needs it.
Our own vision pipeline
Feature detection, description, triangulation, and pose estimation are ReactVision code. There is no OpenCV, no Google Cloud Anchors service, and no third-party SLAM library in the path.
Our own formats and transport
Anchors are stored in a compact binary format the library reads and writes itself, and networking uses the platform's native HTTP and WebSocket stacks. No libcurl, no extra runtime.
One engine, one API key
Nothing is installed on its own. The library ships inside ViroCore, ViroReact exposes it on ViroARSceneNavigator, and an app passes an API key and a project id. Keys never appear in the JavaScript bundle.
A path to running it yourself
The repository carries a reference server for the co-location and replication endpoints, written to run locally or as an edge function. It is the starting point for the on-premises deployments on the enterprise and defence pages.
This is the infrastructure that the Defence page and Enterprise page describe deploying on a customer's own servers.
Status
Released 19 September 2026. First release, v1.0.0, in January 2026.
The one closed-source layer of the stack. Exposed through the MIT-licensed ViroReact API.
As a prebuilt library inside the engine. Nothing to install; add an API key and project id.
Frequently asked questions
What is ReactVision Spatial?
Is ReactVision Spatial open source?
How do I use Spatial in my app?
Does Spatial depend on Google or any third-party vision library?
Which platforms does Spatial support?
What is the difference between the co-location channel and replicated state?
Can Spatial run on our own servers?
Build on Spatial through ViroReact
Cloud Anchors and Geospatial Anchors are documented from the app's side in the ViroReact docs. For co-location, self-hosting, or an on-premises deployment, talk to the team.