Skip to content

v2.55.0

Choose a tag to compare

@doranteseduardo doranteseduardo released this 10 May 20:37
· 51 commits to main since this release
acb0c5e

This release adds first-class Meta Horizon OS support, a new ReactVision Studio integration layer, and a cross-reality JS routing component that lets a single app target phones, tablets and Meta Quest from one React surface. It also ships three Android / iOS stability fixes that unblock the latest Android ABI and resolve two regressions reported by the community.


Highlights

Meta Horizon OS support

VR scenes run natively on Meta Quest 3 / Quest Pro / Quest 2 / Quest 1 via a new OpenXR backend in virocore. The integration is end-to-end and has been device-validated at 90 Hz on Quest 3:

  • Dual-Activity architecture. The same APK ships an Android phone / tablet panel (MainActivity) and an exclusive VR Activity (VRActivity, emitted automatically by the Expo plugin when xRMode: ["QUEST"] is set). Consumers don't have to manage the switch — mount <ViroXRSceneNavigator> and entering/exiting VR is handled for you (see Cross-reality JS layer below).

  • Two simultaneous pointers. Right and left controllers (or tracked hands) each get an independent cyan laser line, hover state, and click resolution. Either side can be controller-tracked or hand-tracked; right is the single-pointer fallback when only one is available.

  • Full input set. Touch controllers (triggers, grips, A/B/X/Y, menu, thumbsticks, haptics), XR_EXT_hand_tracking joints, and XR_FB_hand_tracking_aim for fingertip-aimed pointing. Pinch-to-click and grip-to-grab are detected per hand. B and Menu buttons exit VR; returning from the Quest system menu relaunches automatically, explicit exitVRScene() calls don't.

  • Stable hover and click on small UI targets. Pose hysteresis and a 75 ms hover grace window absorb natural OpenXR aim jitter, so hover state doesn't oscillate and trigger pulls land on first try.

  • Passthrough + recenter. VRModuleOpenXR.setPassthroughEnabled(viewTag, …) and recenterTracking(viewTag) for in-app control.

  • Full lighting pipeline. HDR, PBR, bloom, shadows — all validated at 90 Hz with App=5–6 ms frame time on Quest 3.

  • Fast Refresh works inside VR. Metro Fast Refresh, timers, and requestAnimationFrame keep working when the headset is on, just like in the 2D panel.

  • questAppId plugin option. Sideloaded builds no longer show the system "App Name Unavailable" overlay.

Cross-reality JS layer

Three additions to @reactvision/react-viro collapse the multi-platform story into a one-liner for downstream apps.

  • ViroXRSceneNavigator — auto-detects the platform and mounts the right navigator: ViroVRSceneNavigator on Meta Quest, ViroARSceneNavigator on iOS / non-Quest Android. Pass a single shared scene, or per-platform scenes:

    // Shared scene
    <ViroXRSceneNavigator initialScene={{ scene: MyScene }} />
    
    // Per-platform scenes
    <ViroXRSceneNavigator
      arInitialScene={{ scene: MyARScene }}
      vrInitialScene={{ scene: MyVRScene }}
    />
  • Platform guards on the existing navigators. ViroARSceneNavigator short-circuits with a clean fallback when mounted on Quest instead of trying to start an AR session; ViroVRSceneNavigator does the same on non-Quest Android instead of falling through to the deprecated Google Cardboard split-screen renderer. Both expose an override prop (questFallback / nonQuestFallback) for custom UIs (e.g. a "Launch VR" launcher button).

  • isQuest and hasOpenXRSupport — runtime-detected booleans exported from the package root. Detection is hardware-ID based (Build.MANUFACTURER, BRAND, MODEL via Platform.constants), not module-presence based, so a single APK that bundles Quest support does not misidentify regular phones as Quest.

Multi-pointer hooks

Two helpers for apps that want simple aggregated state without writing the per-source bookkeeping themselves.

import {
  useAnySourceHover,
  useAnySourcePressed,
} from "@reactvision/react-viro";

function MyButton() {
  const [hovered, onHover] = useAnySourceHover();
  const [pressed, onClickState] = useAnySourcePressed();
  return (
    <ViroNode onHover={onHover} onClickState={onClickState} onClick={fire}>
      <ViroQuad
        scale={pressed ? [0.95, 0.95, 0.95] : [1, 1, 1]}
        materials={[hovered ? "btnHover" : "btnIdle"]}
      />
    </ViroNode>
  );
}

Both hooks return [bool, handler] and deduplicate per source ID so a second pointer crossing an already-hovered node does not produce spurious enter/exit toggles in JS state. Apps that do care which specific pointer fired the event (drag-to-controller, single-handed gestures) read source directly from the raw callback.

StudioSceneNavigator

Drop-in component for ReactVision Studio scenes. Fetches a Studio-authored scene by UUID via rvGetScene(sceneId) — auth is the project's API key, wired through the Expo plugin (rvProjectId in app.json):

import { StudioSceneNavigator } from "@reactvision/react-viro";

<StudioSceneNavigator sceneId="abc-123-uuid" style={StyleSheet.absoluteFill} />;

What it renders, end to end:

  • 3D models (GLB / VRX), images, video, and text from the scene's asset registry
  • Per-asset placement (position / rotation / scale) and physics bodies
  • Scene functions: NAVIGATION (push to another scene), ALERT, ANIMATION
  • Collision bindings (asset-pair triggered functions)
  • Animation registry (Studio-authored keyframes pushed into ViroAnimations)
  • Material configs with optional shader modifiers — animated time uniform for animated presets, _rf_vpw / _rf_vph viewport uniforms for screen-space effects, and auto-flagged requiresCameraTexture when the shader samples the camera feed
  • Image-tracking targets and the Viro360Image / Viro360Video background
  • Physics world configs

StudioSceneNavigator is fully cross-reality on Quest — the same sceneId mounts the scene in AR on phones / tablets and in VR on headset, with no consumer-side branching.

Underlying API surface

StudioSceneNavigator is powered by two new endpoints in the ReactVision Cloud Anchor SDK (libreactvisioncca, shipped inside virocore):

  • getScene(sceneId, callback) — returns the full scene response (metadata, assets, animations, collision bindings, scene functions, project info).
  • getSceneAssets(sceneId, callback) — asset-list-only variant for clients that already have scene metadata cached.

Both are exposed to JS via ViroARSceneNavigator.rvGetScene(sceneId) and rvGetSceneAssets(sceneId), and authenticate with the project API key wired through the Expo plugin. Existing Cloud Anchor / Geospatial endpoints are unchanged.


Fixes

  • 16 KB .so page alignment for libvrapi.so (Issue A). The Android 2025 ABI requires all shipped .so files to align to 16 KB pages. libvrapi.so is now repackaged with -Wl,-z,max-page-size=16384, resolving load failures on devices with the new page size.

  • ViroARImageMarker children fixed-on-screen after re-detection (Android, GitHub #465). Models parented to an image marker no longer pin to screen coordinates after the target was lost and re-acquired in v2.54.0. Markers re-anchor cleanly to the detected world pose every time, including subsequent re-detection.

  • iOS ViroPortalScene portal-tree stability (GitHub #452). Continued portal-render-pass hardening on top of the v2.54.0 fix:

    • Portal stencil silhouette no longer drops transparent entry fragments before alpha discard runs.
    • 360° background inside a portal is no longer overwritten by the AR camera background drawn afterwards.
    • The interior of a portal hole no longer reveals the portal interior when the user is outside a nested exit-frame portal.
    • AR occlusion is disabled inside the portal interior so virtual content is no longer discarded by depth-based occlusion when nested.

Compatibility

  • React Native 0.83, Expo 54 + Expo 55 for AR / non-Quest paths (unchanged from 2.54.0).
  • Android Gradle Plugin 8.7+ is required for 16 KB-aligned APKs. Minimum SDK unchanged (24).
  • Meta Quest support requires React Native ≥ 0.83 / Expo ≥ 55. ViroXRSceneNavigator throws an actionable JS error if the runtime is below the threshold. AR continues to work on the Expo 54 + RN 0.79 baseline.
  • Meta Quest support also requires the Quest variant of the Viro Android package — ReactViroPackage(ReactViroPackage.ViroPlatform.QUEST) in MainApplication.kt, plus xRMode: ["QUEST"] in the Expo plugin config. Both are emitted automatically by expo prebuild when the plugin is configured.
  • Recommended install path: Expo Dev Client. Bare React Native is not tested for this release. It should work but will need a substantial amount of manual wiring — MainApplication.kt package registration, the VRActivity Android Activity declaration with the correct intent filter and Quest hardware features, the xRMode Quest manifest features, and iOS Podfile configuration. The Expo plugin generates all of these automatically. Bare RN support will be revisited in a follow-up release.
  • iOS and non-Quest Android code paths are unchanged. Existing apps upgrade with no code changes.

Migration

No breaking JS API changes. Existing ViroARSceneNavigator and ViroVRSceneNavigator usage continues to work. New cross-reality apps should mount <ViroXRSceneNavigator> and wire its onExitViro prop to take the panel back to wherever VR was launched from (e.g. navigation.goBack()). B / Menu buttons exit VR automatically; exitVRScene() from @reactvision/react-viro is only needed for programmatic in-scene exits.