@reactvision/react-viro 2.56.0 → 2.57.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/README.md +8 -0
  2. package/android/react_viro/react_viro-release.aar +0 -0
  3. package/android/viro_renderer/viro_renderer-release.aar +0 -0
  4. package/components/AR/ViroARPlane.tsx +1 -4
  5. package/components/AR/ViroARPlaneSelector.tsx +10 -5
  6. package/components/AR/ViroARScene.tsx +19 -4
  7. package/components/Studio/StudioARScene.tsx +243 -48
  8. package/components/Studio/StudioSceneNavigator.tsx +15 -0
  9. package/components/Studio/VRTStudioModule.ts +10 -0
  10. package/components/Studio/domain/StudioSounds.tsx +46 -0
  11. package/components/Studio/domain/apiRequestHelpers.ts +612 -0
  12. package/components/Studio/domain/collisionBindingsRuntime.ts +17 -2
  13. package/components/Studio/domain/defaultApiRequestExecutor.ts +61 -0
  14. package/components/Studio/domain/expressionEvaluator.ts +603 -0
  15. package/components/Studio/domain/sceneNavigationHandler.ts +770 -20
  16. package/components/Studio/domain/soundManager.ts +141 -0
  17. package/components/Studio/domain/utils.ts +50 -0
  18. package/components/Studio/domain/variableStore.ts +74 -0
  19. package/components/Studio/domain/viroNodeFactory.tsx +175 -44
  20. package/components/Studio/domain/visibilityStore.ts +66 -0
  21. package/components/Studio/index.ts +1 -0
  22. package/components/Studio/types.ts +178 -1
  23. package/components/Utilities/VRModuleOpenXR.ts +35 -0
  24. package/components/Utilities/ViroVersion.ts +1 -1
  25. package/components/ViroAnimatedImage.tsx +1 -0
  26. package/components/ViroFlexView.tsx +1 -0
  27. package/components/ViroImage.tsx +1 -0
  28. package/components/ViroObjectDetector.tsx +230 -0
  29. package/components/ViroParticleEmitter.tsx +2 -0
  30. package/components/ViroPolygon.tsx +2 -1
  31. package/components/ViroQuad.tsx +2 -1
  32. package/components/ViroSurface.tsx +1 -0
  33. package/components/ViroVideo.tsx +1 -0
  34. package/components/ViroXRSceneNavigator.tsx +8 -0
  35. package/dist/components/AR/ViroARPlane.d.ts +1 -1
  36. package/dist/components/AR/ViroARPlane.js +1 -5
  37. package/dist/components/AR/ViroARPlaneSelector.d.ts +1 -1
  38. package/dist/components/AR/ViroARPlaneSelector.js +10 -5
  39. package/dist/components/AR/ViroARScene.d.ts +13 -1
  40. package/dist/components/AR/ViroARScene.js +10 -6
  41. package/dist/components/Studio/StudioARScene.d.ts +3 -0
  42. package/dist/components/Studio/StudioARScene.js +142 -15
  43. package/dist/components/Studio/StudioSceneNavigator.js +14 -0
  44. package/dist/components/Studio/VRTStudioModule.d.ts +6 -0
  45. package/dist/components/Studio/VRTStudioModule.js +10 -0
  46. package/dist/components/Studio/domain/StudioSounds.d.ts +11 -0
  47. package/dist/components/Studio/domain/StudioSounds.js +69 -0
  48. package/dist/components/Studio/domain/apiRequestHelpers.d.ts +180 -0
  49. package/dist/components/Studio/domain/apiRequestHelpers.js +496 -0
  50. package/dist/components/Studio/domain/collisionBindingsRuntime.d.ts +3 -1
  51. package/dist/components/Studio/domain/collisionBindingsRuntime.js +4 -3
  52. package/dist/components/Studio/domain/defaultApiRequestExecutor.d.ts +14 -0
  53. package/dist/components/Studio/domain/defaultApiRequestExecutor.js +58 -0
  54. package/dist/components/Studio/domain/expressionEvaluator.d.ts +108 -0
  55. package/dist/components/Studio/domain/expressionEvaluator.js +520 -0
  56. package/dist/components/Studio/domain/sceneNavigationHandler.d.ts +35 -8
  57. package/dist/components/Studio/domain/sceneNavigationHandler.js +564 -22
  58. package/dist/components/Studio/domain/soundManager.d.ts +52 -0
  59. package/dist/components/Studio/domain/soundManager.js +118 -0
  60. package/dist/components/Studio/domain/utils.d.ts +16 -0
  61. package/dist/components/Studio/domain/utils.js +48 -0
  62. package/dist/components/Studio/domain/variableStore.d.ts +21 -0
  63. package/dist/components/Studio/domain/variableStore.js +65 -0
  64. package/dist/components/Studio/domain/viroNodeFactory.d.ts +4 -9
  65. package/dist/components/Studio/domain/viroNodeFactory.js +84 -25
  66. package/dist/components/Studio/domain/visibilityStore.d.ts +33 -0
  67. package/dist/components/Studio/domain/visibilityStore.js +66 -0
  68. package/dist/components/Studio/index.d.ts +1 -1
  69. package/dist/components/Studio/types.d.ts +150 -1
  70. package/dist/components/Utilities/VRModuleOpenXR.d.ts +19 -0
  71. package/dist/components/Utilities/VRModuleOpenXR.js +16 -0
  72. package/dist/components/Utilities/ViroVersion.d.ts +1 -1
  73. package/dist/components/Utilities/ViroVersion.js +1 -1
  74. package/dist/components/ViroAnimatedImage.js +1 -0
  75. package/dist/components/ViroFlexView.js +1 -0
  76. package/dist/components/ViroImage.js +1 -0
  77. package/dist/components/ViroObjectDetector.d.ts +146 -0
  78. package/dist/components/ViroObjectDetector.js +93 -0
  79. package/dist/components/ViroParticleEmitter.js +2 -0
  80. package/dist/components/ViroPolygon.js +1 -0
  81. package/dist/components/ViroQuad.js +1 -0
  82. package/dist/components/ViroSurface.js +1 -0
  83. package/dist/components/ViroVideo.js +1 -0
  84. package/dist/components/ViroXRSceneNavigator.d.ts +8 -0
  85. package/dist/index.d.ts +6 -4
  86. package/dist/index.js +5 -2
  87. package/dist/plugins/withViro.js +2 -15
  88. package/dist/plugins/withViroAndroid.js +18 -5
  89. package/dist/plugins/withViroIos.js +0 -6
  90. package/index.ts +7 -3
  91. package/ios/ViroReact.podspec +25 -6
  92. package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreCoreMLSemanticsResources.bundle/Info.plist +0 -0
  93. package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreResources.bundle/Info.plist +0 -0
  94. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARFrameiOS.h +9 -0
  95. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARScene.h +14 -1
  96. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSceneDelegateiOS.h +8 -0
  97. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROGLTFLoader.h +5 -1
  98. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROMonocularDepthEstimator.h +10 -0
  99. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROObjectDetector.h +134 -0
  100. package/ios/dist/ViroRenderer/ViroKit.framework/Info.plist +0 -0
  101. package/ios/dist/ViroRenderer/ViroKit.framework/Shaders.dat +1 -1
  102. package/ios/dist/ViroRenderer/ViroKit.framework/ViroKit +0 -0
  103. package/ios/dist/ViroRendererVisionOS/ViroKit.podspec +27 -0
  104. package/ios/dist/include/VRTARScene.h +1 -0
  105. package/ios/dist/lib/libViroReact.a +0 -0
  106. package/package.json +17 -20
@@ -47,7 +47,6 @@ exports.ViroARPlane = void 0;
47
47
  const ViroBase_1 = require("../ViroBase");
48
48
  const React = __importStar(require("react"));
49
49
  const react_native_1 = require("react-native");
50
- const ViroPlatform_1 = require("../Utilities/ViroPlatform");
51
50
  /**
52
51
  * Container for Viro Components anchored to a detected plane.
53
52
  */
@@ -68,10 +67,7 @@ class ViroARPlane extends ViroBase_1.ViroBase {
68
67
  }
69
68
  };
70
69
  render() {
71
- if (ViroPlatform_1.isQuest) {
72
- console.warn("[Viro] ViroARPlane is not supported on Quest and will not render.");
73
- return null;
74
- }
70
+ // Supported on Quest via XR_FB_scene plane anchors (room model). No longer gated.
75
71
  // Uncomment this line to check for misnamed props
76
72
  //checkMisnamedProps("ViroARPlane", this.props);
77
73
  let timeToFuse = undefined;
@@ -293,7 +293,7 @@ export declare class ViroARPlaneSelector extends React.Component<Props, State> {
293
293
  */
294
294
  _worldToLocal: (world: [number, number, number], anchorPosition: [number, number, number], rotationDeg: [number, number, number]) => [number, number, number];
295
295
  _passesAlignmentFilter: (anchor: ViroAnchor) => boolean;
296
- render(): React.JSX.Element | null;
296
+ render(): React.JSX.Element;
297
297
  _renderPlanes(): React.JSX.Element[];
298
298
  }
299
299
  export {};
@@ -285,10 +285,7 @@ class ViroARPlaneSelector extends React.Component {
285
285
  // Render
286
286
  // ---------------------------------------------------------------------------
287
287
  render() {
288
- if (ViroPlatform_1.isQuest) {
289
- console.warn("[Viro] ViroARPlaneSelector is not supported on Quest and will not render.");
290
- return null;
291
- }
288
+ // Supported on Quest via XR_FB_scene plane anchors (room model). No longer gated.
292
289
  return <ViroNode_1.ViroNode>{this._renderPlanes()}</ViroNode_1.ViroNode>;
293
290
  }
294
291
  _renderPlanes() {
@@ -351,6 +348,14 @@ ViroMaterials_1.ViroMaterials.createMaterials({
351
348
  diffuseColor: "rgba(0, 122, 255, 0.5)",
352
349
  blendMode: "Alpha",
353
350
  cullMode: "None",
354
- writesToDepthBuffer: false,
351
+ // Phone: don't write depth, so overlapping/coplanar overlays blend cleanly.
352
+ // Quest: WRITE depth. On Quest's tiled GPU, many non-depth-writing transparent
353
+ // draws break the second (right) eye's render entirely — the whole eye goes
354
+ // black/garbage. Writing depth avoids that path and renders correctly in both
355
+ // eyes (device-confirmed). The plane overlays don't overlap, so depth-writing
356
+ // has no visible downside here. (Engine-level bug — non-depth-writing
357
+ // transparency at quantity breaking stereo — tracked in virocore
358
+ // VROGeometry::updateSubstrate notes / QUEST_SETUP.md.)
359
+ writesToDepthBuffer: ViroPlatform_1.isQuest,
355
360
  },
356
361
  });
@@ -17,6 +17,14 @@ type Props = ViroCommonProps & {
17
17
  onTrackingUpdated?: (state: ViroTrackingState, reason: ViroTrackingReason) => void;
18
18
  onPlatformUpdate?: (platformInfoViro: ViroPlatformInfo) => void;
19
19
  onAmbientLightUpdate?: (update: ViroAmbientLightInfo) => void;
20
+ /**
21
+ * Fired once when depth data (LiDAR or monocular depth estimation) first becomes
22
+ * available for the session — i.e. hit tests can now return DepthPoints. Useful for
23
+ * hiding an "initializing depth…" state instead of acting on early, inaccurate
24
+ * feature points. On devices using monocular depth estimation, this can take a few
25
+ * seconds after the AR screen appears while the model warms up.
26
+ */
27
+ onDepthReady?: () => void;
20
28
  /**
21
29
  * Describes the acoustic properties of the room around the user
22
30
  */
@@ -47,6 +55,10 @@ export declare class ViroARScene extends ViroBase<Props> {
47
55
  * Returns object w/ "intensity" and "color" keys
48
56
  */
49
57
  _onAmbientLightUpdate: (event: NativeSyntheticEvent<ViroAmbientLightUpdateEvent>) => void;
58
+ /**
59
+ * Fired once when depth data first becomes available for hit testing.
60
+ */
61
+ _onDepthReady: (_event: NativeSyntheticEvent<{}>) => void;
50
62
  _onAnchorFound: (event: NativeSyntheticEvent<ViroARAnchorFoundEvent>) => void;
51
63
  _onAnchorUpdated: (event: NativeSyntheticEvent<ViroARAnchorUpdatedEvent>) => void;
52
64
  _onAnchorRemoved: (event: NativeSyntheticEvent<ViroARAnchorRemovedEvent>) => void;
@@ -101,6 +113,6 @@ export declare class ViroARScene extends ViroBase<Props> {
101
113
  up: any[];
102
114
  }>;
103
115
  getCameraPositionAsync: () => Promise<any>;
104
- render(): React.JSX.Element | null;
116
+ render(): React.JSX.Element;
105
117
  }
106
118
  export {};
@@ -52,7 +52,6 @@ const react_native_1 = require("react-native");
52
52
  const resolveAssetSource_1 = __importDefault(require("react-native/Libraries/Image/resolveAssetSource"));
53
53
  const ViroBase_1 = require("../ViroBase");
54
54
  const ViroConstants_1 = require("../ViroConstants");
55
- const ViroPlatform_1 = require("../Utilities/ViroPlatform");
56
55
  const ViroCameraModule = react_native_1.NativeModules.ViroCameraModule;
57
56
  class ViroARScene extends ViroBase_1.ViroBase {
58
57
  onTrackingFirstInitialized = false;
@@ -176,6 +175,12 @@ class ViroARScene extends ViroBase_1.ViroBase {
176
175
  this.props.onAmbientLightUpdate &&
177
176
  this.props.onAmbientLightUpdate(event.nativeEvent.ambientLightInfo);
178
177
  };
178
+ /**
179
+ * Fired once when depth data first becomes available for hit testing.
180
+ */
181
+ _onDepthReady = (_event) => {
182
+ this.props.onDepthReady && this.props.onDepthReady();
183
+ };
179
184
  _onAnchorFound = (event) => {
180
185
  // TODO: this is in a different format than the other onAnchorFound methods
181
186
  this.props.onAnchorFound &&
@@ -284,10 +289,8 @@ class ViroARScene extends ViroBase_1.ViroBase {
284
289
  return await ViroCameraModule.getCameraPosition((0, react_native_1.findNodeHandle)(this));
285
290
  };
286
291
  render() {
287
- if (ViroPlatform_1.isQuest) {
288
- console.warn("[Viro] ViroARScene is not supported on Quest and will not render. Use ViroScene instead.");
289
- return null;
290
- }
292
+ // On Meta Quest, ViroARScene renders as a mixed-reality scene through the
293
+ // OpenXR renderer (passthrough + XR_FB_scene plane anchors). No longer gated.
291
294
  // Uncomment this line to check for misnamed props
292
295
  //checkMisnamedProps("ViroARScene", this.props);
293
296
  // Since anchorDetectionTypes can be either a string or an array, convert the string to a 1-element array.
@@ -334,7 +337,7 @@ class ViroARScene extends ViroBase_1.ViroBase {
334
337
  },
335
338
  }}>
336
339
  <VRTARScene {...this.props} canHover={this.props.onHover != undefined} canClick={this.props.onClick != undefined ||
337
- this.props.onClickState != undefined} canTouch={this.props.onTouch != undefined} canScroll={this.props.onScroll != undefined} canSwipe={this.props.onSwipe != undefined} canDrag={this.props.onDrag != undefined} canPinch={this.props.onPinch != undefined} canRotate={this.props.onRotate != undefined} canFuse={this.props.onFuse != undefined} canCameraARHitTest={this.props.onCameraARHitTest != undefined} canARPointCloudUpdate={this.props.onARPointCloudUpdate != undefined} canCameraTransformUpdate={this.props.onCameraTransformUpdate != undefined} onHoverViro={this._onHover} onClickViro={this._onClickState} onClick={undefined} onTouchViro={this._onTouch} onScrollViro={this._onScroll} onSwipeViro={this._onSwipe} onDragViro={this._onDrag} onPinchViro={this._onPinch} onRotateViro={this._onRotate} onFuseViro={this._onFuse} onCameraARHitTestViro={this._onCameraARHitTest} onARPointCloudUpdateViro={this._onARPointCloudUpdate} onCameraTransformUpdateViro={this._onCameraTransformUpdate} onPlatformUpdateViro={this._onPlatformUpdate} onTrackingUpdatedViro={this._onTrackingUpdated} onAmbientLightUpdateViro={this._onAmbientLightUpdate} onAnchorFoundViro={this._onAnchorFound} onAnchorUpdatedViro={this._onAnchorUpdated} onAnchorRemovedViro={this._onAnchorRemoved} timeToFuse={timeToFuse} anchorDetectionTypes={anchorDetectionTypes} displayPointCloud={displayPointCloud} pointCloudImage={pointCloudImage} pointCloudScale={pointCloudScale} pointCloudMaxPoints={pointCloudMaxPoints}/>
340
+ this.props.onClickState != undefined} canTouch={this.props.onTouch != undefined} canScroll={this.props.onScroll != undefined} canSwipe={this.props.onSwipe != undefined} canDrag={this.props.onDrag != undefined} canPinch={this.props.onPinch != undefined} canRotate={this.props.onRotate != undefined} canFuse={this.props.onFuse != undefined} canCameraARHitTest={this.props.onCameraARHitTest != undefined} canARPointCloudUpdate={this.props.onARPointCloudUpdate != undefined} canCameraTransformUpdate={this.props.onCameraTransformUpdate != undefined} onHoverViro={this._onHover} onClickViro={this._onClickState} onClick={undefined} onTouchViro={this._onTouch} onScrollViro={this._onScroll} onSwipeViro={this._onSwipe} onDragViro={this._onDrag} onPinchViro={this._onPinch} onRotateViro={this._onRotate} onFuseViro={this._onFuse} onCameraARHitTestViro={this._onCameraARHitTest} onARPointCloudUpdateViro={this._onARPointCloudUpdate} onCameraTransformUpdateViro={this._onCameraTransformUpdate} onPlatformUpdateViro={this._onPlatformUpdate} onTrackingUpdatedViro={this._onTrackingUpdated} onAmbientLightUpdateViro={this._onAmbientLightUpdate} onDepthReadyViro={this._onDepthReady} onAnchorFoundViro={this._onAnchorFound} onAnchorUpdatedViro={this._onAnchorUpdated} onAnchorRemovedViro={this._onAnchorRemoved} timeToFuse={timeToFuse} anchorDetectionTypes={anchorDetectionTypes} displayPointCloud={displayPointCloud} pointCloudImage={pointCloudImage} pointCloudScale={pointCloudScale} pointCloudMaxPoints={pointCloudMaxPoints}/>
338
341
  </ViroSceneContext_1.ViroSceneContext.Provider>);
339
342
  }
340
343
  }
@@ -368,6 +371,7 @@ ViroARScene, {
368
371
  onTrackingInitializedViro: true,
369
372
  onTrackingUpdatedViro: true,
370
373
  onAmbientLightUpdateViro: true,
374
+ onDepthReadyViro: true,
371
375
  onAnchorFoundViro: true,
372
376
  onAnchorUpdatedViro: true,
373
377
  onAnchorRemovedViro: true,
@@ -1,4 +1,5 @@
1
1
  import * as React from "react";
2
+ import { StudioVariableStore } from "./domain/variableStore";
2
3
  import { StudioSceneResponse } from "./types";
3
4
  interface StudioARSceneProps {
4
5
  sceneNavigator?: any;
@@ -6,6 +7,8 @@ interface StudioARSceneProps {
6
7
  onReady?: () => void;
7
8
  onError?: (err: Error) => void;
8
9
  onSceneChange?: (sceneId: string, sceneName: string) => void;
10
+ /** Session-scoped store owned by the navigator; survives scene pushes. */
11
+ variableStore?: StudioVariableStore;
9
12
  }
10
13
  /**
11
14
  * Outer gate: keeps the hooks-bearing inner component out of the tree until
@@ -51,7 +51,12 @@ const collisionBindingsRuntime_1 = require("./domain/collisionBindingsRuntime");
51
51
  const collisionPairKey_1 = require("./domain/collisionPairKey");
52
52
  const triggerImageRegistry_1 = require("./domain/triggerImageRegistry");
53
53
  const viroNodeFactory_1 = require("./domain/viroNodeFactory");
54
+ const defaultApiRequestExecutor_1 = require("./domain/defaultApiRequestExecutor");
54
55
  const sceneNavigationHandler_1 = require("./domain/sceneNavigationHandler");
56
+ const variableStore_1 = require("./domain/variableStore");
57
+ const visibilityStore_1 = require("./domain/visibilityStore");
58
+ const soundManager_1 = require("./domain/soundManager");
59
+ const StudioSounds_1 = require("./domain/StudioSounds");
55
60
  const studioMaterials_1 = require("./domain/studioMaterials");
56
61
  const useStudioShaderTimeUniforms_1 = require("./domain/useStudioShaderTimeUniforms");
57
62
  const useStudioShaderViewportUniforms_1 = require("./domain/useStudioShaderViewportUniforms");
@@ -70,8 +75,82 @@ const StudioARScene = (props) => {
70
75
  };
71
76
  exports.StudioARScene = StudioARScene;
72
77
  const StudioARSceneInner = (props) => {
73
- const { sceneNavigator, sceneData, onReady, onSceneChange } = props;
78
+ const { sceneNavigator, sceneData, onReady, onSceneChange, variableStore } = props;
74
79
  const { scene, assets, animations, collision_bindings, functions } = sceneData;
80
+ // ─── Sequence scheduler ───────────────────────────────────────────────────
81
+ // One per scene. Drives WAIT steps; cancelled on unmount and on navigation so
82
+ // a pending WAIT never fires into a torn-down or replaced scene.
83
+ const schedulerRef = (0, react_1.useRef)(null);
84
+ if (schedulerRef.current === null) {
85
+ schedulerRef.current = new sceneNavigationHandler_1.SequenceScheduler();
86
+ }
87
+ (0, react_1.useEffect)(() => {
88
+ return () => {
89
+ schedulerRef.current?.dispose();
90
+ schedulerRef.current = null;
91
+ // dispose() bumps the scheduler generation first; reset() then clears any
92
+ // pending sound backstop timers and fires their callbacks, which now
93
+ // no-op via the generation guard so unmount can't advance a waited step.
94
+ soundManagerRef.current?.reset();
95
+ };
96
+ }, []);
97
+ // ─── Variable store ───────────────────────────────────────────────────────
98
+ // Normally passed down by the navigator (session-scoped); hosts mounting this
99
+ // scene directly get a scene-local fallback. Seeding happens here, at instance
100
+ // init, so values exist before any effect dispatches on_load. seed() is
101
+ // initialize-if-absent, hence idempotent and strict-mode safe.
102
+ const variableStoreRef = (0, react_1.useRef)(null);
103
+ if (variableStoreRef.current === null) {
104
+ variableStoreRef.current = variableStore ?? new variableStore_1.StudioVariableStore();
105
+ variableStoreRef.current.seed(sceneData.variables ?? []);
106
+ }
107
+ // ─── Visibility store ─────────────────────────────────────────────────────
108
+ // Scene-scoped (asset placements are per-scene), keyed by asset id. Seeded
109
+ // from each asset's author-time hidden_on_load default; Set Visibility
110
+ // actions flip it at runtime. Re-seeded on scene change so a persisted
111
+ // instance doesn't carry stale visibility across a navigation.
112
+ const visibilityStoreRef = (0, react_1.useRef)(null);
113
+ if (visibilityStoreRef.current === null) {
114
+ visibilityStoreRef.current = new visibilityStore_1.StudioVisibilityStore();
115
+ visibilityStoreRef.current.seed(assets);
116
+ }
117
+ (0, react_1.useEffect)(() => {
118
+ visibilityStoreRef.current?.reseed(assets);
119
+ // eslint-disable-next-line react-hooks/exhaustive-deps
120
+ }, [scene.id]);
121
+ // ─── Sound manager ────────────────────────────────────────────────────────
122
+ // Per-scene. PLAY/STOP scene-function actions drive it; <StudioSounds> renders
123
+ // the active list. Reset on scene change so sounds don't leak across a
124
+ // navigation (sounds, unlike variables, are not session-scoped).
125
+ const soundManagerRef = (0, react_1.useRef)(null);
126
+ if (soundManagerRef.current === null) {
127
+ soundManagerRef.current = new soundManager_1.StudioSoundManager();
128
+ }
129
+ (0, react_1.useEffect)(() => {
130
+ soundManagerRef.current?.reset();
131
+ // eslint-disable-next-line react-hooks/exhaustive-deps
132
+ }, [scene.id]);
133
+ // Position for a spatial PLAY: look up the placed target asset (matches the
134
+ // node factory's position derivation, position_z defaulting to -2).
135
+ const getAssetPosition = (0, react_1.useCallback)((assetId) => {
136
+ const a = assets.find((x) => x.id === assetId);
137
+ if (!a)
138
+ return undefined;
139
+ return [a.position_x ?? 0, a.position_y ?? 0, a.position_z ?? -2];
140
+ }, [assets]);
141
+ const runtimeCtx = (0, react_1.useMemo)(() => ({
142
+ scheduler: schedulerRef.current,
143
+ variableStore: variableStoreRef.current,
144
+ apiRequestExecutor: defaultApiRequestExecutor_1.defaultApiRequestExecutor,
145
+ visibilityStore: visibilityStoreRef.current,
146
+ soundManager: soundManagerRef.current,
147
+ getAssetPosition,
148
+ }), [getAssetPosition]);
149
+ // Cancel this scene's pending WAITs before handing off to the next scene.
150
+ const handleSceneChange = (0, react_1.useCallback)((sceneId, sceneName) => {
151
+ schedulerRef.current?.cancelAll();
152
+ onSceneChange?.(sceneId, sceneName);
153
+ }, [onSceneChange]);
75
154
  // ─── Material registration ────────────────────────────────────────────────
76
155
  const materialsRegisteredRef = (0, react_1.useRef)(false);
77
156
  if (!materialsRegisteredRef.current) {
@@ -103,7 +182,10 @@ const StudioARSceneInner = (props) => {
103
182
  const triggerAnimation = (0, react_1.useCallback)((targetAssetId, animationKey) => {
104
183
  // Viro's animation prop is edge-triggered on false→true. Force false first,
105
184
  // then flip to true on the next frame so a re-trigger of the same key fires.
106
- setAnimOverrides((prev) => ({ ...prev, [targetAssetId]: { key: animationKey, run: false } }));
185
+ setAnimOverrides((prev) => ({
186
+ ...prev,
187
+ [targetAssetId]: { key: animationKey, run: false },
188
+ }));
107
189
  const handle = requestAnimationFrame(() => {
108
190
  triggerHandlesRef.current.delete(handle);
109
191
  setAnimOverrides((prev) => {
@@ -148,21 +230,29 @@ const StudioARSceneInner = (props) => {
148
230
  interruptible: activeAnim.interruptible,
149
231
  delay: activeAnim.delay_ms ?? 0,
150
232
  onStart: activeAnim.on_start_function
151
- ? () => (0, sceneNavigationHandler_1.executeOnLoadFunction)(activeAnim.on_start_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key))
233
+ ? () => (0, sceneNavigationHandler_1.executeOnLoadFunction)(activeAnim.on_start_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key), handleSceneChange, runtimeCtx)
152
234
  : undefined,
153
235
  onFinish: activeAnim.on_finish_function
154
- ? () => (0, sceneNavigationHandler_1.executeOnLoadFunction)(activeAnim.on_finish_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key))
236
+ ? () => (0, sceneNavigationHandler_1.executeOnLoadFunction)(activeAnim.on_finish_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key), handleSceneChange, runtimeCtx)
155
237
  : undefined,
156
238
  };
157
239
  }
158
240
  return states;
159
- }, [animations, animOverrides, loadedAssetIds, functions, sceneNavigator]);
241
+ }, [
242
+ animations,
243
+ animOverrides,
244
+ loadedAssetIds,
245
+ functions,
246
+ sceneNavigator,
247
+ handleSceneChange,
248
+ runtimeCtx,
249
+ ]);
160
250
  // ─── on_load_function ─────────────────────────────────────────────────────
161
251
  const onLoadExecutedRef = (0, react_1.useRef)(false);
162
252
  (0, react_1.useEffect)(() => {
163
253
  if (scene.on_load_function && !onLoadExecutedRef.current) {
164
254
  onLoadExecutedRef.current = true;
165
- (0, sceneNavigationHandler_1.executeOnLoadFunction)(scene.on_load_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key), onSceneChange);
255
+ (0, sceneNavigationHandler_1.executeOnLoadFunction)(scene.on_load_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key), handleSceneChange, runtimeCtx);
166
256
  }
167
257
  }, [scene.id]);
168
258
  // ─── Collision bindings ───────────────────────────────────────────────────
@@ -188,8 +278,15 @@ const StudioARSceneInner = (props) => {
188
278
  const getCollisionHandler = (0, react_1.useCallback)((placementId) => {
189
279
  if (!collisionAssetIds.has(placementId))
190
280
  return undefined;
191
- return (0, collisionBindingsRuntime_1.createPlacementCollisionHandler)(placementId, bindingsByPairKey, sceneNavigator, animations, collisionCooldownRef, (id, key) => triggerAnimationRef.current(id, key), onSceneChange);
192
- }, [bindingsByPairKey, collisionAssetIds, sceneNavigator, animations]);
281
+ return (0, collisionBindingsRuntime_1.createPlacementCollisionHandler)(placementId, bindingsByPairKey, sceneNavigator, animations, collisionCooldownRef, (id, key) => triggerAnimationRef.current(id, key), handleSceneChange, runtimeCtx);
282
+ }, [
283
+ bindingsByPairKey,
284
+ collisionAssetIds,
285
+ sceneNavigator,
286
+ animations,
287
+ handleSceneChange,
288
+ runtimeCtx,
289
+ ]);
193
290
  // ─── Trigger image targets ────────────────────────────────────────────────
194
291
  const { planeAssets, imageTriggeredAssets } = (0, react_1.useMemo)(() => {
195
292
  const plane = assets.filter((a) => !a.trigger_image_url);
@@ -221,7 +318,9 @@ const StudioARSceneInner = (props) => {
221
318
  };
222
319
  }, [imageTriggeredAssets]);
223
320
  // ─── Ready callback ───────────────────────────────────────────────────────
224
- (0, react_1.useEffect)(() => { onReady?.(); }, []);
321
+ (0, react_1.useEffect)(() => {
322
+ onReady?.();
323
+ }, []);
225
324
  // ─── Render helpers ───────────────────────────────────────────────────────
226
325
  const maxModels = react_native_1.Platform.OS === "android" ? ANDROID_MAX_3D_MODELS : IOS_MAX_3D_MODELS;
227
326
  const renderedPlaneAssets = (0, react_1.useMemo)(() => {
@@ -235,10 +334,20 @@ const StudioARSceneInner = (props) => {
235
334
  return null;
236
335
  }
237
336
  }
238
- return (0, viroNodeFactory_1.createNode)(asset, sceneNavigator, animations, scene, (id, key) => triggerAnimationRef.current(id, key), animationStates, handleAssetLoaded, getCollisionHandler(asset.id), onSceneChange);
337
+ return (0, viroNodeFactory_1.createNode)(asset, sceneNavigator, animations, scene, (id, key) => triggerAnimationRef.current(id, key), animationStates, handleAssetLoaded, getCollisionHandler(asset.id), handleSceneChange, runtimeCtx);
239
338
  })
240
339
  .filter(Boolean);
241
- }, [planeAssets, sceneNavigator, animations, animationStates, handleAssetLoaded, getCollisionHandler, maxModels, onSceneChange]);
340
+ }, [
341
+ planeAssets,
342
+ sceneNavigator,
343
+ animations,
344
+ animationStates,
345
+ handleAssetLoaded,
346
+ getCollisionHandler,
347
+ maxModels,
348
+ handleSceneChange,
349
+ runtimeCtx,
350
+ ]);
242
351
  const renderedImageTriggeredAssets = (0, react_1.useMemo)(() => {
243
352
  if (ViroPlatform_1.isQuest)
244
353
  return [];
@@ -247,7 +356,7 @@ const StudioARSceneInner = (props) => {
247
356
  const targetName = urlToTargetName.get(asset.trigger_image_url);
248
357
  if (!targetName)
249
358
  return null;
250
- const node = (0, viroNodeFactory_1.createNode)(asset, sceneNavigator, animations, scene, (id, key) => triggerAnimationRef.current(id, key), animationStates, handleAssetLoaded, getCollisionHandler(asset.id), onSceneChange);
359
+ const node = (0, viroNodeFactory_1.createNode)(asset, sceneNavigator, animations, scene, (id, key) => triggerAnimationRef.current(id, key), animationStates, handleAssetLoaded, getCollisionHandler(asset.id), handleSceneChange, runtimeCtx);
251
360
  if (!node)
252
361
  return null;
253
362
  return (<ViroARImageMarker_1.ViroARImageMarker key={asset.id} target={targetName}>
@@ -255,7 +364,17 @@ const StudioARSceneInner = (props) => {
255
364
  </ViroARImageMarker_1.ViroARImageMarker>);
256
365
  })
257
366
  .filter(Boolean);
258
- }, [urlToTargetName, imageTriggeredAssets, sceneNavigator, animations, animationStates, handleAssetLoaded, getCollisionHandler, onSceneChange]);
367
+ }, [
368
+ urlToTargetName,
369
+ imageTriggeredAssets,
370
+ sceneNavigator,
371
+ animations,
372
+ animationStates,
373
+ handleAssetLoaded,
374
+ getCollisionHandler,
375
+ handleSceneChange,
376
+ runtimeCtx,
377
+ ]);
259
378
  // ─── Plane detection (AR only) ────────────────────────────────────────────
260
379
  const planeDetectionMode = (scene.plane_detection ?? "NONE").toUpperCase();
261
380
  const planeAlignment = (scene.plane_direction ?? "Horizontal");
@@ -283,14 +402,22 @@ const StudioARSceneInner = (props) => {
283
402
  const physicsWorld = physicsWorldConfig?.enabled
284
403
  ? (0, physicsConfig_1.buildViroPhysicsWorld)(physicsWorldConfig)
285
404
  : undefined;
286
- const physicsProps = physicsWorld ? { physicsWorld: physicsWorld } : {};
405
+ const physicsProps = physicsWorld
406
+ ? { physicsWorld: physicsWorld }
407
+ : {};
287
408
  // ─── Render ───────────────────────────────────────────────────────────────
288
409
  const children = (<>
289
410
  {ViroPlatform_1.isQuest && <ViroController_1.ViroController controllerVisibility reticleVisibility/>}
290
411
  <ViroAmbientLight_1.ViroAmbientLight color="#ffffff" intensity={1000}/>
291
412
  {renderAssets()}
292
413
  {renderedImageTriggeredAssets}
293
- {assets.length === 0 && (<ViroText_1.ViroText text="No assets to display" position={[0, 0, -2]} style={{ fontFamily: "Arial", fontSize: 16, color: "#CCCCCC", textAlign: "center" }}/>)}
414
+ <StudioSounds_1.StudioSounds manager={soundManagerRef.current}/>
415
+ {assets.length === 0 && (<ViroText_1.ViroText text="No assets to display" position={[0, 0, -2]} style={{
416
+ fontFamily: "Arial",
417
+ fontSize: 16,
418
+ color: "#CCCCCC",
419
+ textAlign: "center",
420
+ }}/>)}
294
421
  </>);
295
422
  if (ViroPlatform_1.isQuest) {
296
423
  return <ViroScene_1.ViroScene {...physicsProps}>{children}</ViroScene_1.ViroScene>;
@@ -43,6 +43,7 @@ const ViroXRSceneNavigator_1 = require("../ViroXRSceneNavigator");
43
43
  const ViroPlatform_1 = require("../Utilities/ViroPlatform");
44
44
  const animationRegistry_1 = require("./domain/animationRegistry");
45
45
  const studioMaterials_1 = require("./domain/studioMaterials");
46
+ const variableStore_1 = require("./domain/variableStore");
46
47
  const StudioARScene_1 = require("./StudioARScene");
47
48
  const VRTStudioModule_1 = require("./VRTStudioModule");
48
49
  function LoadingARScene() { return <ViroARScene_1.ViroARScene />; }
@@ -72,6 +73,18 @@ const styles = react_native_1.StyleSheet.create({
72
73
  function StudioSceneNavigator({ sceneId, worldAlignment = "Gravity", autofocus = true, style, onSceneReady, onError, onSceneChange, onExitViro, }) {
73
74
  const navigatorRef = (0, react_1.useRef)(null);
74
75
  const loadedSceneIdRef = (0, react_1.useRef)(null);
76
+ // Session-scoped variable store: outlives every scene push, resets when the
77
+ // navigator (= the AR/VR session) unmounts.
78
+ const variableStoreRef = (0, react_1.useRef)(null);
79
+ if (variableStoreRef.current === null) {
80
+ variableStoreRef.current = new variableStore_1.StudioVariableStore();
81
+ }
82
+ (0, react_1.useEffect)(() => {
83
+ return () => {
84
+ variableStoreRef.current?.reset();
85
+ variableStoreRef.current = null;
86
+ };
87
+ }, []);
75
88
  const onSceneReadyRef = (0, react_1.useRef)(onSceneReady);
76
89
  const onErrorRef = (0, react_1.useRef)(onError);
77
90
  const onSceneChangeRef = (0, react_1.useRef)(onSceneChange);
@@ -137,6 +150,7 @@ function StudioSceneNavigator({ sceneId, worldAlignment = "Gravity", autofocus =
137
150
  sceneData,
138
151
  onReady: onSceneReadyRef.current,
139
152
  onSceneChange: onSceneChangeRef.current,
153
+ variableStore: variableStoreRef.current,
140
154
  },
141
155
  };
142
156
  if (ViroPlatform_1.isQuest) {
@@ -12,4 +12,10 @@ export declare const VRTStudioModule: {
12
12
  rvGetProject: () => Promise<StudioModuleResult>;
13
13
  /** Returns the configured project ID, or null if not set. */
14
14
  rvGetProjectId: () => Promise<string | null>;
15
+ /**
16
+ * POSTs a pre-serialised scene-api-request body ({function_id, variables})
17
+ * to the egress proxy, authenticated with the app's RVApiKey. The resolved
18
+ * `data` is the proxy's outcome envelope JSON.
19
+ */
20
+ rvStudioApiRequest: (bodyJson: string) => Promise<StudioModuleResult>;
15
21
  };
@@ -28,4 +28,14 @@ exports.VRTStudioModule = {
28
28
  return Promise.resolve(null);
29
29
  return native.rvGetProjectId();
30
30
  },
31
+ /**
32
+ * POSTs a pre-serialised scene-api-request body ({function_id, variables})
33
+ * to the egress proxy, authenticated with the app's RVApiKey. The resolved
34
+ * `data` is the proxy's outcome envelope JSON.
35
+ */
36
+ rvStudioApiRequest: (bodyJson) => {
37
+ if (!native)
38
+ return Promise.resolve(NOT_AVAILABLE);
39
+ return native.rvStudioApiRequest(bodyJson);
40
+ },
31
41
  };
@@ -0,0 +1,11 @@
1
+ import * as React from "react";
2
+ import { StudioSoundManager } from "./soundManager";
3
+ /**
4
+ * Renders every actively-playing sound from the manager, re-painting the whole
5
+ * list on any change (like the reactive variable-text nodes). Non-looping sounds
6
+ * remove themselves on finish; any sound removes itself on error so a clip that
7
+ * fails to load releases a waiting step instead of stalling the sequence.
8
+ */
9
+ export declare const StudioSounds: React.FC<{
10
+ manager: StudioSoundManager;
11
+ }>;
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.StudioSounds = void 0;
37
+ const React = __importStar(require("react"));
38
+ const ViroSound_1 = require("../../ViroSound");
39
+ const ViroSpatialSound_1 = require("../../ViroSpatialSound");
40
+ /**
41
+ * Renders every actively-playing sound from the manager, re-painting the whole
42
+ * list on any change (like the reactive variable-text nodes). Non-looping sounds
43
+ * remove themselves on finish; any sound removes itself on error so a clip that
44
+ * fails to load releases a waiting step instead of stalling the sequence.
45
+ */
46
+ const StudioSounds = ({ manager, }) => {
47
+ const [, force] = React.useReducer((n) => n + 1, 0);
48
+ React.useEffect(() => manager.subscribe(force), [manager]);
49
+ return (<>
50
+ {manager.getActive().map((s) => {
51
+ const shared = {
52
+ source: { uri: s.url },
53
+ volume: s.volume,
54
+ loop: s.loop,
55
+ paused: false,
56
+ onFinish: () => {
57
+ if (!s.loop)
58
+ manager.remove(s.playId);
59
+ },
60
+ onError: (e) => {
61
+ console.warn(`[Studio] Sound failed: ${s.audioAssetId} (#${s.playId})`, e?.nativeEvent?.error);
62
+ manager.remove(s.playId);
63
+ },
64
+ };
65
+ return s.position ? (<ViroSpatialSound_1.ViroSpatialSound key={s.playId} position={s.position} {...shared}/>) : (<ViroSound_1.ViroSound key={s.playId} {...shared}/>);
66
+ })}
67
+ </>);
68
+ };
69
+ exports.StudioSounds = StudioSounds;