@reactvision/react-viro 2.53.1 → 2.55.0

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 (153) hide show
  1. package/README.md +85 -46
  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/ViroARCamera.tsx +5 -0
  5. package/components/AR/ViroARImageMarker.tsx +5 -0
  6. package/components/AR/ViroARObjectMarker.tsx +5 -0
  7. package/components/AR/ViroARPlane.tsx +5 -0
  8. package/components/AR/ViroARPlaneSelector.tsx +5 -0
  9. package/components/AR/ViroARScene.tsx +5 -0
  10. package/components/AR/ViroARSceneNavigator.tsx +84 -0
  11. package/components/AR/ViroCommonProps.ts +11 -0
  12. package/components/Material/ViroMaterials.ts +51 -0
  13. package/components/Studio/StudioARScene.tsx +368 -0
  14. package/components/Studio/StudioSceneNavigator.tsx +191 -0
  15. package/components/Studio/VRTStudioModule.ts +40 -0
  16. package/components/Studio/domain/animationRegistry.ts +86 -0
  17. package/components/Studio/domain/collisionBindingsRuntime.ts +93 -0
  18. package/components/Studio/domain/collisionPairKey.ts +15 -0
  19. package/components/Studio/domain/dragConfiguration.ts +48 -0
  20. package/components/Studio/domain/materialConfig.ts +276 -0
  21. package/components/Studio/domain/physicsConfig.ts +204 -0
  22. package/components/Studio/domain/sceneNavigationHandler.ts +150 -0
  23. package/components/Studio/domain/studioMaterials.ts +33 -0
  24. package/components/Studio/domain/triggerImageRegistry.ts +64 -0
  25. package/components/Studio/domain/useStudioShaderTimeUniforms.ts +51 -0
  26. package/components/Studio/domain/useStudioShaderViewportUniforms.ts +52 -0
  27. package/components/Studio/domain/viroNodeFactory.tsx +323 -0
  28. package/components/Studio/index.ts +18 -0
  29. package/components/Studio/types.ts +164 -0
  30. package/components/Types/ViroEvents.ts +53 -0
  31. package/components/Utilities/VRModuleOpenXR.ts +50 -0
  32. package/components/Utilities/VRQuestNavigatorBridge.ts +168 -0
  33. package/components/Utilities/ViroPlatform.ts +52 -0
  34. package/components/Utilities/ViroUtils.tsx +48 -0
  35. package/components/Utilities/ViroVersion.ts +1 -1
  36. package/components/Utilities/useAnySourceHover.ts +55 -0
  37. package/components/Utilities/useAnySourcePressed.ts +70 -0
  38. package/components/Viro360Image.tsx +7 -0
  39. package/components/ViroQuestEntryPoint.tsx +79 -0
  40. package/components/ViroVRSceneNavigator.tsx +44 -19
  41. package/components/ViroXRSceneNavigator.tsx +217 -0
  42. package/components/VisionOS/ViroVisionOSModule.ts +93 -0
  43. package/dist/components/AR/ViroARCamera.d.ts +1 -1
  44. package/dist/components/AR/ViroARCamera.js +5 -0
  45. package/dist/components/AR/ViroARImageMarker.d.ts +1 -1
  46. package/dist/components/AR/ViroARImageMarker.js +5 -0
  47. package/dist/components/AR/ViroARObjectMarker.d.ts +1 -1
  48. package/dist/components/AR/ViroARObjectMarker.js +5 -0
  49. package/dist/components/AR/ViroARPlane.d.ts +1 -1
  50. package/dist/components/AR/ViroARPlane.js +5 -0
  51. package/dist/components/AR/ViroARPlaneSelector.d.ts +1 -1
  52. package/dist/components/AR/ViroARPlaneSelector.js +5 -0
  53. package/dist/components/AR/ViroARScene.d.ts +1 -1
  54. package/dist/components/AR/ViroARScene.js +5 -0
  55. package/dist/components/AR/ViroARSceneNavigator.d.ts +36 -0
  56. package/dist/components/AR/ViroARSceneNavigator.js +41 -0
  57. package/dist/components/AR/ViroCommonProps.d.ts +11 -0
  58. package/dist/components/Material/ViroMaterials.d.ts +12 -0
  59. package/dist/components/Material/ViroMaterials.js +25 -0
  60. package/dist/components/ReactVisionClient.d.ts +25 -0
  61. package/dist/components/ReactVisionClient.js +11 -0
  62. package/dist/components/Studio/StudioARScene.d.ts +15 -0
  63. package/dist/components/Studio/StudioARScene.js +299 -0
  64. package/dist/components/Studio/StudioSceneNavigator.d.ts +31 -0
  65. package/dist/components/Studio/StudioSceneNavigator.js +174 -0
  66. package/dist/components/Studio/VRTStudioModule.d.ts +15 -0
  67. package/dist/components/Studio/VRTStudioModule.js +31 -0
  68. package/dist/components/Studio/domain/animationRegistry.d.ts +11 -0
  69. package/dist/components/Studio/domain/animationRegistry.js +67 -0
  70. package/dist/components/Studio/domain/collisionBindingsRuntime.d.ts +21 -0
  71. package/dist/components/Studio/domain/collisionBindingsRuntime.js +54 -0
  72. package/dist/components/Studio/domain/collisionPairKey.d.ts +8 -0
  73. package/dist/components/Studio/domain/collisionPairKey.js +15 -0
  74. package/dist/components/Studio/domain/dragConfiguration.d.ts +20 -0
  75. package/dist/components/Studio/domain/dragConfiguration.js +37 -0
  76. package/dist/components/Studio/domain/materialConfig.d.ts +56 -0
  77. package/dist/components/Studio/domain/materialConfig.js +239 -0
  78. package/dist/components/Studio/domain/physicsConfig.d.ts +69 -0
  79. package/dist/components/Studio/domain/physicsConfig.js +165 -0
  80. package/dist/components/Studio/domain/sceneNavigationHandler.d.ts +12 -0
  81. package/dist/components/Studio/domain/sceneNavigationHandler.js +112 -0
  82. package/dist/components/Studio/domain/studioMaterials.d.ts +6 -0
  83. package/dist/components/Studio/domain/studioMaterials.js +30 -0
  84. package/dist/components/Studio/domain/triggerImageRegistry.d.ts +13 -0
  85. package/dist/components/Studio/domain/triggerImageRegistry.js +47 -0
  86. package/dist/components/Studio/domain/useStudioShaderTimeUniforms.d.ts +6 -0
  87. package/dist/components/Studio/domain/useStudioShaderTimeUniforms.js +48 -0
  88. package/dist/components/Studio/domain/useStudioShaderViewportUniforms.d.ts +6 -0
  89. package/dist/components/Studio/domain/useStudioShaderViewportUniforms.js +48 -0
  90. package/dist/components/Studio/domain/viroNodeFactory.d.ts +28 -0
  91. package/dist/components/Studio/domain/viroNodeFactory.js +193 -0
  92. package/dist/components/Studio/index.d.ts +3 -0
  93. package/dist/components/Studio/index.js +7 -0
  94. package/dist/components/Studio/types.d.ts +149 -0
  95. package/dist/components/Studio/types.js +4 -0
  96. package/dist/components/Types/ViroEvents.d.ts +49 -1
  97. package/dist/components/Types/ViroEvents.js +1 -0
  98. package/dist/components/Utilities/VRModuleOpenXR.d.ts +32 -0
  99. package/dist/components/Utilities/VRModuleOpenXR.js +44 -0
  100. package/dist/components/Utilities/VRQuestNavigatorBridge.d.ts +85 -0
  101. package/dist/components/Utilities/VRQuestNavigatorBridge.js +124 -0
  102. package/dist/components/Utilities/ViroPlatform.d.ts +10 -0
  103. package/dist/components/Utilities/ViroPlatform.js +43 -0
  104. package/dist/components/Utilities/ViroUtils.d.ts +19 -0
  105. package/dist/components/Utilities/ViroUtils.js +34 -0
  106. package/dist/components/Utilities/ViroVersion.d.ts +1 -1
  107. package/dist/components/Utilities/ViroVersion.js +1 -1
  108. package/dist/components/Utilities/useAnySourceHover.d.ts +36 -0
  109. package/dist/components/Utilities/useAnySourceHover.js +48 -0
  110. package/dist/components/Utilities/useAnySourcePressed.d.ts +37 -0
  111. package/dist/components/Utilities/useAnySourcePressed.js +61 -0
  112. package/dist/components/Viro360Image.d.ts +7 -0
  113. package/dist/components/ViroQuestEntryPoint.d.ts +13 -0
  114. package/dist/components/ViroQuestEntryPoint.js +104 -0
  115. package/dist/components/ViroVRSceneNavigator.d.ts +24 -10
  116. package/dist/components/ViroVRSceneNavigator.js +21 -18
  117. package/dist/components/ViroXRSceneNavigator.d.ts +54 -0
  118. package/dist/components/ViroXRSceneNavigator.js +173 -0
  119. package/dist/components/VisionOS/ViroVisionOSModule.d.ts +65 -0
  120. package/dist/components/VisionOS/ViroVisionOSModule.js +91 -0
  121. package/dist/index.d.ts +16 -3
  122. package/dist/index.js +34 -2
  123. package/dist/plugins/withViro.d.ts +28 -1
  124. package/dist/plugins/withViroAndroid.js +312 -7
  125. package/dist/plugins/withViroIos.js +17 -8
  126. package/dist/plugins/withViroVisionOS.d.ts +24 -0
  127. package/dist/plugins/withViroVisionOS.js +265 -0
  128. package/index.ts +66 -0
  129. package/ios/ViroReact.podspec +15 -5
  130. package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreCoreMLSemanticsResources.bundle/Info.plist +0 -0
  131. package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreResources.bundle/Info.plist +0 -0
  132. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSession.h +30 -1
  133. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSessioniOS.h +16 -0
  134. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROGLTFLoader.h +34 -0
  135. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROInputControllerBase.h +74 -0
  136. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROInputType.h +11 -3
  137. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROMaterial.h +29 -0
  138. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROMorpher.h +4 -0
  139. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROPlatformUtil.h +13 -0
  140. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROPortal.h +17 -0
  141. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRORenderContext.h +41 -0
  142. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRORenderer.h +23 -0
  143. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROSemantics.h +14 -0
  144. package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROViewAR.h +11 -0
  145. package/ios/dist/ViroRenderer/ViroKit.framework/Info.plist +0 -0
  146. package/ios/dist/ViroRenderer/ViroKit.framework/Shaders.dat +1 -1
  147. package/ios/dist/ViroRenderer/ViroKit.framework/ViroKit +0 -0
  148. package/ios/dist/ViroRenderer/ViroKit.podspec +5 -0
  149. package/ios/dist/include/VRT360Image.h +1 -0
  150. package/ios/dist/include/VRTARSceneNavigator.h +7 -0
  151. package/ios/dist/include/VRTStudioModule.h +6 -0
  152. package/ios/dist/lib/libViroReact.a +0 -0
  153. package/package.json +8 -8
@@ -0,0 +1,299 @@
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.StudioARScene = void 0;
37
+ const React = __importStar(require("react"));
38
+ const react_1 = require("react");
39
+ const react_native_1 = require("react-native");
40
+ const ViroAmbientLight_1 = require("../ViroAmbientLight");
41
+ const ViroARImageMarker_1 = require("../AR/ViroARImageMarker");
42
+ const ViroARPlane_1 = require("../AR/ViroARPlane");
43
+ const ViroARPlaneSelector_1 = require("../AR/ViroARPlaneSelector");
44
+ const ViroARScene_1 = require("../AR/ViroARScene");
45
+ const ViroScene_1 = require("../ViroScene");
46
+ const ViroText_1 = require("../ViroText");
47
+ const ViroController_1 = require("../ViroController");
48
+ const ViroPlatform_1 = require("../Utilities/ViroPlatform");
49
+ const animationRegistry_1 = require("./domain/animationRegistry");
50
+ const collisionBindingsRuntime_1 = require("./domain/collisionBindingsRuntime");
51
+ const collisionPairKey_1 = require("./domain/collisionPairKey");
52
+ const triggerImageRegistry_1 = require("./domain/triggerImageRegistry");
53
+ const viroNodeFactory_1 = require("./domain/viroNodeFactory");
54
+ const sceneNavigationHandler_1 = require("./domain/sceneNavigationHandler");
55
+ const studioMaterials_1 = require("./domain/studioMaterials");
56
+ const useStudioShaderTimeUniforms_1 = require("./domain/useStudioShaderTimeUniforms");
57
+ const useStudioShaderViewportUniforms_1 = require("./domain/useStudioShaderViewportUniforms");
58
+ const physicsConfig_1 = require("./domain/physicsConfig");
59
+ const ANDROID_MAX_3D_MODELS = 3;
60
+ const IOS_MAX_3D_MODELS = 10;
61
+ /**
62
+ * Outer gate: keeps the hooks-bearing inner component out of the tree until
63
+ * sceneData is available, avoiding a Rules of Hooks violation.
64
+ */
65
+ const StudioARScene = (props) => {
66
+ if (!props.sceneData) {
67
+ return ViroPlatform_1.isQuest ? <ViroScene_1.ViroScene /> : <ViroARScene_1.ViroARScene />;
68
+ }
69
+ return <StudioARSceneInner {...props} sceneData={props.sceneData}/>;
70
+ };
71
+ exports.StudioARScene = StudioARScene;
72
+ const StudioARSceneInner = (props) => {
73
+ const { sceneNavigator, sceneData, onReady, onSceneChange } = props;
74
+ const { scene, assets, animations, collision_bindings, functions } = sceneData;
75
+ // ─── Material registration ────────────────────────────────────────────────
76
+ const materialsRegisteredRef = (0, react_1.useRef)(false);
77
+ if (!materialsRegisteredRef.current) {
78
+ (0, studioMaterials_1.registerStudioMaterialsForAssets)(assets);
79
+ materialsRegisteredRef.current = true;
80
+ }
81
+ (0, useStudioShaderTimeUniforms_1.useStudioShaderTimeUniforms)(assets);
82
+ (0, useStudioShaderViewportUniforms_1.useStudioShaderViewportUniforms)(assets);
83
+ // ─── Animation registration ───────────────────────────────────────────────
84
+ const registeredKeyRef = (0, react_1.useRef)(null);
85
+ const animationsKey = animations.map((a) => a.animation_key).join(",");
86
+ if (animations.length > 0 && registeredKeyRef.current !== animationsKey) {
87
+ registeredKeyRef.current = animationsKey;
88
+ (0, animationRegistry_1.registerSceneAnimations)(animations);
89
+ }
90
+ // ─── Animation runtime state ──────────────────────────────────────────────
91
+ const [animOverrides, setAnimOverrides] = (0, react_1.useState)({});
92
+ const [loadedAssetIds, setLoadedAssetIds] = (0, react_1.useState)({});
93
+ const handleAssetLoaded = (0, react_1.useCallback)((assetId) => {
94
+ setLoadedAssetIds((prev) => prev[assetId] ? prev : { ...prev, [assetId]: true });
95
+ }, []);
96
+ const triggerHandlesRef = (0, react_1.useRef)(new Set());
97
+ (0, react_1.useEffect)(() => {
98
+ return () => {
99
+ triggerHandlesRef.current.forEach((id) => cancelAnimationFrame(id));
100
+ triggerHandlesRef.current.clear();
101
+ };
102
+ }, []);
103
+ const triggerAnimation = (0, react_1.useCallback)((targetAssetId, animationKey) => {
104
+ // Viro's animation prop is edge-triggered on false→true. Force false first,
105
+ // 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 } }));
107
+ const handle = requestAnimationFrame(() => {
108
+ triggerHandlesRef.current.delete(handle);
109
+ setAnimOverrides((prev) => {
110
+ const current = prev[targetAssetId];
111
+ if (!current || current.key !== animationKey || current.run)
112
+ return prev;
113
+ return { ...prev, [targetAssetId]: { key: animationKey, run: true } };
114
+ });
115
+ });
116
+ triggerHandlesRef.current.add(handle);
117
+ }, []);
118
+ const triggerAnimationRef = (0, react_1.useRef)(triggerAnimation);
119
+ triggerAnimationRef.current = triggerAnimation;
120
+ // ─── Computed animation props per asset ──────────────────────────────────
121
+ const animationStates = (0, react_1.useMemo)(() => {
122
+ const states = {};
123
+ const animsByAsset = new Map();
124
+ for (const anim of animations) {
125
+ const list = animsByAsset.get(anim.target_asset_id) ?? [];
126
+ list.push(anim);
127
+ animsByAsset.set(anim.target_asset_id, list);
128
+ }
129
+ for (const [assetId, anims] of animsByAsset) {
130
+ const override = animOverrides[assetId];
131
+ let activeAnim;
132
+ let run;
133
+ if (override) {
134
+ const triggered = anims.find((a) => a.animation_key === override.key);
135
+ if (!triggered)
136
+ continue;
137
+ activeAnim = triggered;
138
+ run = override.run && !!loadedAssetIds[assetId];
139
+ }
140
+ else {
141
+ activeAnim = anims[0];
142
+ run = false;
143
+ }
144
+ states[assetId] = {
145
+ name: activeAnim.animation_key,
146
+ run,
147
+ loop: activeAnim.loop,
148
+ interruptible: activeAnim.interruptible,
149
+ delay: activeAnim.delay_ms ?? 0,
150
+ onStart: activeAnim.on_start_function
151
+ ? () => (0, sceneNavigationHandler_1.executeOnLoadFunction)(activeAnim.on_start_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key))
152
+ : undefined,
153
+ onFinish: activeAnim.on_finish_function
154
+ ? () => (0, sceneNavigationHandler_1.executeOnLoadFunction)(activeAnim.on_finish_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key))
155
+ : undefined,
156
+ };
157
+ }
158
+ return states;
159
+ }, [animations, animOverrides, loadedAssetIds, functions, sceneNavigator]);
160
+ // ─── on_load_function ─────────────────────────────────────────────────────
161
+ const onLoadExecutedRef = (0, react_1.useRef)(false);
162
+ (0, react_1.useEffect)(() => {
163
+ if (scene.on_load_function && !onLoadExecutedRef.current) {
164
+ onLoadExecutedRef.current = true;
165
+ (0, sceneNavigationHandler_1.executeOnLoadFunction)(scene.on_load_function, functions, sceneNavigator, animations, (id, key) => triggerAnimationRef.current(id, key), onSceneChange);
166
+ }
167
+ }, [scene.id]);
168
+ // ─── Collision bindings ───────────────────────────────────────────────────
169
+ const bindingsByPairKey = (0, react_1.useMemo)(() => {
170
+ const m = new Map();
171
+ for (const b of collision_bindings) {
172
+ const key = (0, collisionPairKey_1.collisionPairKey)(b.asset_x_id, b.asset_y_id);
173
+ const list = m.get(key) ?? [];
174
+ list.push(b);
175
+ m.set(key, list);
176
+ }
177
+ return m;
178
+ }, [collision_bindings]);
179
+ const collisionAssetIds = (0, react_1.useMemo)(() => {
180
+ const s = new Set();
181
+ for (const b of collision_bindings) {
182
+ s.add(b.asset_x_id);
183
+ s.add(b.asset_y_id);
184
+ }
185
+ return s;
186
+ }, [collision_bindings]);
187
+ const collisionCooldownRef = (0, react_1.useRef)(new Map());
188
+ const getCollisionHandler = (0, react_1.useCallback)((placementId) => {
189
+ if (!collisionAssetIds.has(placementId))
190
+ 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]);
193
+ // ─── Trigger image targets ────────────────────────────────────────────────
194
+ const { planeAssets, imageTriggeredAssets } = (0, react_1.useMemo)(() => {
195
+ const plane = assets.filter((a) => !a.trigger_image_url);
196
+ const imgTriggered = assets.filter((a) => !!a.trigger_image_url);
197
+ return { planeAssets: plane, imageTriggeredAssets: imgTriggered };
198
+ }, [assets]);
199
+ const [urlToTargetName, setUrlToTargetName] = (0, react_1.useState)(() => new Map());
200
+ const prevTargetNamesRef = (0, react_1.useRef)([]);
201
+ (0, react_1.useEffect)(() => {
202
+ if (ViroPlatform_1.isQuest) {
203
+ if (imageTriggeredAssets.length > 0) {
204
+ console.warn("[Studio] Image-triggered assets are not supported on Quest — skipping.");
205
+ }
206
+ return;
207
+ }
208
+ if (imageTriggeredAssets.length === 0) {
209
+ (0, triggerImageRegistry_1.cleanupTriggerImageTargets)(prevTargetNamesRef.current);
210
+ prevTargetNamesRef.current = [];
211
+ setUrlToTargetName(new Map());
212
+ return;
213
+ }
214
+ const map = (0, triggerImageRegistry_1.registerTriggerImageTargets)(imageTriggeredAssets);
215
+ const targetNames = [...map.values()];
216
+ prevTargetNamesRef.current = targetNames;
217
+ setUrlToTargetName(map);
218
+ return () => {
219
+ (0, triggerImageRegistry_1.cleanupTriggerImageTargets)(targetNames);
220
+ prevTargetNamesRef.current = [];
221
+ };
222
+ }, [imageTriggeredAssets]);
223
+ // ─── Ready callback ───────────────────────────────────────────────────────
224
+ (0, react_1.useEffect)(() => { onReady?.(); }, []);
225
+ // ─── Render helpers ───────────────────────────────────────────────────────
226
+ const maxModels = react_native_1.Platform.OS === "android" ? ANDROID_MAX_3D_MODELS : IOS_MAX_3D_MODELS;
227
+ const renderedPlaneAssets = (0, react_1.useMemo)(() => {
228
+ let modelCount = 0;
229
+ return planeAssets
230
+ .map((asset) => {
231
+ if (asset.asset_type_name === "3D-MODEL") {
232
+ modelCount++;
233
+ if (modelCount > maxModels) {
234
+ console.warn(`[Studio] Skipping 3D model "${asset.name}" — ${react_native_1.Platform.OS} limit (${maxModels}) reached`);
235
+ return null;
236
+ }
237
+ }
238
+ return (0, viroNodeFactory_1.createNode)(asset, sceneNavigator, animations, scene, (id, key) => triggerAnimationRef.current(id, key), animationStates, handleAssetLoaded, getCollisionHandler(asset.id), onSceneChange);
239
+ })
240
+ .filter(Boolean);
241
+ }, [planeAssets, sceneNavigator, animations, animationStates, handleAssetLoaded, getCollisionHandler, maxModels, onSceneChange]);
242
+ const renderedImageTriggeredAssets = (0, react_1.useMemo)(() => {
243
+ if (ViroPlatform_1.isQuest)
244
+ return [];
245
+ return imageTriggeredAssets
246
+ .map((asset) => {
247
+ const targetName = urlToTargetName.get(asset.trigger_image_url);
248
+ if (!targetName)
249
+ 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);
251
+ if (!node)
252
+ return null;
253
+ return (<ViroARImageMarker_1.ViroARImageMarker key={asset.id} target={targetName}>
254
+ {node}
255
+ </ViroARImageMarker_1.ViroARImageMarker>);
256
+ })
257
+ .filter(Boolean);
258
+ }, [urlToTargetName, imageTriggeredAssets, sceneNavigator, animations, animationStates, handleAssetLoaded, getCollisionHandler, onSceneChange]);
259
+ // ─── Plane detection (AR only) ────────────────────────────────────────────
260
+ const planeDetectionMode = (scene.plane_detection ?? "NONE").toUpperCase();
261
+ const planeAlignment = (scene.plane_direction ?? "Horizontal");
262
+ const renderAssets = () => {
263
+ if (ViroPlatform_1.isQuest) {
264
+ if (planeDetectionMode !== "NONE") {
265
+ console.warn(`[Studio] Plane detection (${planeDetectionMode}) is not supported on Quest — rendering assets without plane anchor.`);
266
+ }
267
+ return <>{renderedPlaneAssets}</>;
268
+ }
269
+ if (planeDetectionMode === "AUTOMATIC") {
270
+ return (<ViroARPlane_1.ViroARPlane minHeight={0.1} minWidth={0.1} alignment={planeAlignment}>
271
+ {renderedPlaneAssets}
272
+ </ViroARPlane_1.ViroARPlane>);
273
+ }
274
+ if (planeDetectionMode === "MANUAL") {
275
+ return (<ViroARPlaneSelector_1.ViroARPlaneSelector minHeight={0.1} minWidth={0.1} alignment={planeAlignment}>
276
+ {renderedPlaneAssets}
277
+ </ViroARPlaneSelector_1.ViroARPlaneSelector>);
278
+ }
279
+ return <>{renderedPlaneAssets}</>;
280
+ };
281
+ // ─── Physics world ────────────────────────────────────────────────────────
282
+ const physicsWorldConfig = (0, physicsConfig_1.parsePhysicsWorldConfig)(scene.physics_world_config);
283
+ const physicsWorld = physicsWorldConfig?.enabled
284
+ ? (0, physicsConfig_1.buildViroPhysicsWorld)(physicsWorldConfig)
285
+ : undefined;
286
+ const physicsProps = physicsWorld ? { physicsWorld: physicsWorld } : {};
287
+ // ─── Render ───────────────────────────────────────────────────────────────
288
+ const children = (<>
289
+ {ViroPlatform_1.isQuest && <ViroController_1.ViroController controllerVisibility reticleVisibility/>}
290
+ <ViroAmbientLight_1.ViroAmbientLight color="#ffffff" intensity={1000}/>
291
+ {renderAssets()}
292
+ {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" }}/>)}
294
+ </>);
295
+ if (ViroPlatform_1.isQuest) {
296
+ return <ViroScene_1.ViroScene {...physicsProps}>{children}</ViroScene_1.ViroScene>;
297
+ }
298
+ return <ViroARScene_1.ViroARScene {...physicsProps}>{children}</ViroARScene_1.ViroARScene>;
299
+ };
@@ -0,0 +1,31 @@
1
+ import * as React from "react";
2
+ import { ViewStyle } from "react-native";
3
+ interface StudioSceneNavigatorProps {
4
+ /**
5
+ * UUID of a specific scene to load. If omitted, the navigator fetches the
6
+ * project configured in the app manifest and uses its opening scene.
7
+ */
8
+ sceneId?: string;
9
+ worldAlignment?: "Gravity" | "GravityAndHeading" | "Camera";
10
+ autofocus?: boolean;
11
+ style?: ViewStyle;
12
+ onSceneReady?: () => void;
13
+ onError?: (err: Error) => void;
14
+ onSceneChange?: (sceneId: string, sceneName: string) => void;
15
+ onExitViro?: () => void;
16
+ }
17
+ /**
18
+ * Cross-reality Studio scene navigator. Renders a Studio-authored scene on
19
+ * both AR devices (iOS / non-Quest Android) and Meta Quest (VR).
20
+ *
21
+ * Opening-scene resolution order:
22
+ * 1. `sceneId` prop → use it directly
23
+ * 2. Native project (RVProjectId from manifest) → use `opening_scene.id`
24
+ * 3. Fallback → first scene in the project's scene list
25
+ *
26
+ * On Quest, ViroXRSceneNavigator is not rendered until the scene data is
27
+ * ready. This means VRActivity always launches with the actual content scene
28
+ * as its initial scene, avoiding the LoadingVRScene → replace timing race.
29
+ */
30
+ export declare function StudioSceneNavigator({ sceneId, worldAlignment, autofocus, style, onSceneReady, onError, onSceneChange, onExitViro, }: StudioSceneNavigatorProps): React.JSX.Element;
31
+ export {};
@@ -0,0 +1,174 @@
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.StudioSceneNavigator = StudioSceneNavigator;
37
+ const React = __importStar(require("react"));
38
+ const react_1 = require("react");
39
+ const react_native_1 = require("react-native");
40
+ const ViroARScene_1 = require("../AR/ViroARScene");
41
+ const ViroScene_1 = require("../ViroScene");
42
+ const ViroXRSceneNavigator_1 = require("../ViroXRSceneNavigator");
43
+ const ViroPlatform_1 = require("../Utilities/ViroPlatform");
44
+ const animationRegistry_1 = require("./domain/animationRegistry");
45
+ const studioMaterials_1 = require("./domain/studioMaterials");
46
+ const StudioARScene_1 = require("./StudioARScene");
47
+ const VRTStudioModule_1 = require("./VRTStudioModule");
48
+ function LoadingARScene() { return <ViroARScene_1.ViroARScene />; }
49
+ function LoadingVRScene() { return <ViroScene_1.ViroScene />; }
50
+ const styles = react_native_1.StyleSheet.create({
51
+ loader: {
52
+ position: "absolute",
53
+ top: 0, left: 0, right: 0, bottom: 0,
54
+ justifyContent: "center",
55
+ alignItems: "center",
56
+ backgroundColor: "#000000",
57
+ },
58
+ });
59
+ /**
60
+ * Cross-reality Studio scene navigator. Renders a Studio-authored scene on
61
+ * both AR devices (iOS / non-Quest Android) and Meta Quest (VR).
62
+ *
63
+ * Opening-scene resolution order:
64
+ * 1. `sceneId` prop → use it directly
65
+ * 2. Native project (RVProjectId from manifest) → use `opening_scene.id`
66
+ * 3. Fallback → first scene in the project's scene list
67
+ *
68
+ * On Quest, ViroXRSceneNavigator is not rendered until the scene data is
69
+ * ready. This means VRActivity always launches with the actual content scene
70
+ * as its initial scene, avoiding the LoadingVRScene → replace timing race.
71
+ */
72
+ function StudioSceneNavigator({ sceneId, worldAlignment = "Gravity", autofocus = true, style, onSceneReady, onError, onSceneChange, onExitViro, }) {
73
+ const navigatorRef = (0, react_1.useRef)(null);
74
+ const loadedSceneIdRef = (0, react_1.useRef)(null);
75
+ const onSceneReadyRef = (0, react_1.useRef)(onSceneReady);
76
+ const onErrorRef = (0, react_1.useRef)(onError);
77
+ const onSceneChangeRef = (0, react_1.useRef)(onSceneChange);
78
+ onSceneReadyRef.current = onSceneReady;
79
+ onErrorRef.current = onError;
80
+ onSceneChangeRef.current = onSceneChange;
81
+ // On Quest: holds the resolved scene entry. ViroXRSceneNavigator is not
82
+ // rendered until this is non-null, so VRActivity always launches into content.
83
+ const [vrSceneEntry, setVrSceneEntry] = (0, react_1.useState)(null);
84
+ const resolveSceneId = (0, react_1.useCallback)(async () => {
85
+ if (sceneId)
86
+ return sceneId;
87
+ const projectResult = await VRTStudioModule_1.VRTStudioModule.rvGetProject();
88
+ if (!projectResult.success) {
89
+ throw new Error(projectResult.error ?? "rvGetProject failed");
90
+ }
91
+ if (typeof projectResult.data !== "string") {
92
+ throw new Error("rvGetProject returned no data");
93
+ }
94
+ const { project } = JSON.parse(projectResult.data);
95
+ if (project.opening_scene?.id) {
96
+ return project.opening_scene.id;
97
+ }
98
+ if (project.scenes.length > 0) {
99
+ return project.scenes[0].id;
100
+ }
101
+ throw new Error(`Project ${project.id} has no scenes`);
102
+ }, [sceneId]);
103
+ const loadScene = (0, react_1.useCallback)(async (isCancelled) => {
104
+ await new Promise((resolve) => requestAnimationFrame(() => resolve()));
105
+ if (isCancelled())
106
+ return;
107
+ const resolvedSceneId = await resolveSceneId();
108
+ if (isCancelled())
109
+ return;
110
+ if (loadedSceneIdRef.current === resolvedSceneId)
111
+ return;
112
+ const result = await VRTStudioModule_1.VRTStudioModule.rvGetScene(resolvedSceneId);
113
+ if (isCancelled())
114
+ return;
115
+ if (!result.success) {
116
+ throw new Error(result.error ?? "rvGetScene failed");
117
+ }
118
+ if (typeof result.data !== "string") {
119
+ throw new Error("rvGetScene returned no data");
120
+ }
121
+ const sceneData = JSON.parse(result.data);
122
+ if (isCancelled())
123
+ return;
124
+ loadedSceneIdRef.current = resolvedSceneId;
125
+ // On Quest: pre-register animations and materials before VRActivity launches.
126
+ // This mirrors the module-level registration pattern used by XRSceneContent —
127
+ // native registrations complete before any Viro components mount, eliminating
128
+ // the race between registerAnimations/createMaterials native calls and the
129
+ // Fabric commit that creates those components.
130
+ if (ViroPlatform_1.isQuest) {
131
+ (0, animationRegistry_1.registerSceneAnimations)(sceneData.animations);
132
+ (0, studioMaterials_1.registerStudioMaterialsForAssets)(sceneData.assets);
133
+ }
134
+ const entry = {
135
+ scene: StudioARScene_1.StudioARScene,
136
+ passProps: {
137
+ sceneData,
138
+ onReady: onSceneReadyRef.current,
139
+ onSceneChange: onSceneChangeRef.current,
140
+ },
141
+ };
142
+ if (ViroPlatform_1.isQuest) {
143
+ // On Quest: setting vrSceneEntry triggers ViroXRSceneNavigator to mount
144
+ // with StudioARScene as vrInitialScene — VRActivity gets content immediately.
145
+ setVrSceneEntry(entry);
146
+ }
147
+ else {
148
+ navigatorRef.current?.arSceneNavigator?.push(entry);
149
+ }
150
+ }, [resolveSceneId]);
151
+ (0, react_1.useEffect)(() => {
152
+ let cancelled = false;
153
+ const isCancelled = () => cancelled;
154
+ loadScene(isCancelled).catch((e) => {
155
+ if (cancelled)
156
+ return;
157
+ const err = e instanceof Error ? e : new Error(String(e));
158
+ const handler = onErrorRef.current;
159
+ if (handler)
160
+ handler(err);
161
+ else
162
+ console.error("[Studio] Failed to load scene:", err);
163
+ });
164
+ return () => { cancelled = true; };
165
+ }, [sceneId, loadScene]);
166
+ // On Quest: show a spinner until scene data is ready, then mount
167
+ // ViroXRSceneNavigator (which launches VRActivity with content immediately).
168
+ if (ViroPlatform_1.isQuest && !vrSceneEntry) {
169
+ return (<react_native_1.View style={styles.loader}>
170
+ <react_native_1.ActivityIndicator size="large" color="#ffffff"/>
171
+ </react_native_1.View>);
172
+ }
173
+ return (<ViroXRSceneNavigator_1.ViroXRSceneNavigator ref={navigatorRef} arInitialScene={{ scene: LoadingARScene }} vrInitialScene={vrSceneEntry ?? { scene: LoadingVRScene }} worldAlignment={worldAlignment} autofocus={autofocus} onExitViro={onExitViro} style={style ?? react_native_1.StyleSheet.absoluteFill}/>);
174
+ }
@@ -0,0 +1,15 @@
1
+ export interface StudioModuleResult {
2
+ success: boolean;
3
+ data?: string;
4
+ error?: string;
5
+ }
6
+ export declare const VRTStudioModule: {
7
+ rvGetScene: (sceneId: string) => Promise<StudioModuleResult>;
8
+ /**
9
+ * Fetches the project configured in the app manifest (Android: `com.reactvision.RVProjectId`,
10
+ * iOS: `RVProjectId`). The project ID is baked in by the Expo plugin at build time.
11
+ */
12
+ rvGetProject: () => Promise<StudioModuleResult>;
13
+ /** Returns the configured project ID, or null if not set. */
14
+ rvGetProjectId: () => Promise<string | null>;
15
+ };
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VRTStudioModule = void 0;
4
+ const react_native_1 = require("react-native");
5
+ const native = react_native_1.NativeModules.VRTStudio;
6
+ const NOT_AVAILABLE = {
7
+ success: false,
8
+ error: "VRTStudio native module not available",
9
+ };
10
+ exports.VRTStudioModule = {
11
+ rvGetScene: (sceneId) => {
12
+ if (!native)
13
+ return Promise.resolve(NOT_AVAILABLE);
14
+ return native.rvGetScene(sceneId);
15
+ },
16
+ /**
17
+ * Fetches the project configured in the app manifest (Android: `com.reactvision.RVProjectId`,
18
+ * iOS: `RVProjectId`). The project ID is baked in by the Expo plugin at build time.
19
+ */
20
+ rvGetProject: () => {
21
+ if (!native)
22
+ return Promise.resolve(NOT_AVAILABLE);
23
+ return native.rvGetProject();
24
+ },
25
+ /** Returns the configured project ID, or null if not set. */
26
+ rvGetProjectId: () => {
27
+ if (!native)
28
+ return Promise.resolve(null);
29
+ return native.rvGetProjectId();
30
+ },
31
+ };
@@ -0,0 +1,11 @@
1
+ import { StudioAnimation } from "../types";
2
+ /**
3
+ * Builds the Viro animation registry object from StudioAnimation rows.
4
+ * The properties field is already in Viro's native keyframe format.
5
+ */
6
+ export declare function buildViroAnimationRegistry(animations: StudioAnimation[]): Record<string, unknown>;
7
+ /**
8
+ * Registers all scene animations with ViroReact.
9
+ * Must be called before any animated Viro components mount.
10
+ */
11
+ export declare function registerSceneAnimations(animations: StudioAnimation[]): void;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildViroAnimationRegistry = buildViroAnimationRegistry;
4
+ exports.registerSceneAnimations = registerSceneAnimations;
5
+ const ViroAnimations_1 = require("../../Animation/ViroAnimations");
6
+ const MAX_CONCURRENT_ANIMATIONS = 10;
7
+ const MAX_DURATION_MS = 30_000;
8
+ const MAX_POSITION_ABS = 1_000;
9
+ const MAX_SCALE = 100;
10
+ const MIN_SCALE = 0.01;
11
+ /**
12
+ * Builds the Viro animation registry object from StudioAnimation rows.
13
+ * The properties field is already in Viro's native keyframe format.
14
+ */
15
+ function buildViroAnimationRegistry(animations) {
16
+ const registry = {};
17
+ for (const anim of animations) {
18
+ warnAnimationPerformance(anim);
19
+ registry[anim.animation_key] = {
20
+ properties: anim.properties,
21
+ duration: anim.duration_ms ?? 1000,
22
+ delay: anim.delay_ms ?? 0,
23
+ ...(anim.easing ? { easing: anim.easing } : {}),
24
+ };
25
+ }
26
+ return registry;
27
+ }
28
+ /**
29
+ * Registers all scene animations with ViroReact.
30
+ * Must be called before any animated Viro components mount.
31
+ */
32
+ function registerSceneAnimations(animations) {
33
+ if (animations.length === 0)
34
+ return;
35
+ if (animations.length > MAX_CONCURRENT_ANIMATIONS) {
36
+ console.warn(`[Studio/Animation] Scene has ${animations.length} animations. ` +
37
+ `Recommended max is ${MAX_CONCURRENT_ANIMATIONS} for smooth performance.`);
38
+ }
39
+ const registry = buildViroAnimationRegistry(animations);
40
+ ViroAnimations_1.ViroAnimations.registerAnimations(registry);
41
+ console.log(`[Studio/Animation] Registered ${Object.keys(registry).length} animation(s): ${Object.keys(registry).join(", ")}`);
42
+ }
43
+ function warnAnimationPerformance(anim) {
44
+ if ((anim.duration_ms ?? 0) > MAX_DURATION_MS) {
45
+ console.warn(`[Studio/Animation] "${anim.animation_key}" duration ${anim.duration_ms}ms exceeds ` +
46
+ `recommended max of ${MAX_DURATION_MS}ms.`);
47
+ }
48
+ const props = anim.properties;
49
+ if (!props || typeof props !== "object")
50
+ return;
51
+ for (const key of Object.keys(props)) {
52
+ const val = props[key];
53
+ if (typeof val !== "number")
54
+ continue;
55
+ if (key === "scaleX" || key === "scaleY" || key === "scaleZ") {
56
+ if (val > MAX_SCALE || val < MIN_SCALE) {
57
+ console.warn(`[Studio/Animation] "${anim.animation_key}" ${key}=${val} is outside ` +
58
+ `recommended range [${MIN_SCALE}, ${MAX_SCALE}].`);
59
+ }
60
+ }
61
+ if (key === "positionX" || key === "positionY" || key === "positionZ") {
62
+ if (Math.abs(val) > MAX_POSITION_ABS) {
63
+ console.warn(`[Studio/Animation] "${anim.animation_key}" ${key}=${val} is far from origin.`);
64
+ }
65
+ }
66
+ }
67
+ }
@@ -0,0 +1,21 @@
1
+ import { MutableRefObject } from "react";
2
+ import { StudioAnimation, StudioCollisionBinding } from "../types";
3
+ /**
4
+ * Dispatches scene functions for collision bindings matching the canonical pair.
5
+ * Cooldown prevents per-frame spam while physics contacts overlap.
6
+ */
7
+ export declare function dispatchCollisionBindingActions(params: {
8
+ selfPlacementId: string;
9
+ otherTag: string;
10
+ bindingsByPairKey: Map<string, StudioCollisionBinding[]>;
11
+ sceneNavigator?: unknown;
12
+ animations: StudioAnimation[];
13
+ onSceneChange?: (sceneId: string, sceneName: string) => void;
14
+ onAnimationTrigger?: (targetAssetId: string, animationKey: string) => void;
15
+ cooldownMs?: number;
16
+ lastFiredRef: MutableRefObject<Map<string, number>>;
17
+ }): void;
18
+ /**
19
+ * Returns an onCollision handler for a given placement asset ID.
20
+ */
21
+ export declare function createPlacementCollisionHandler(placementId: string, bindingsByPairKey: Map<string, StudioCollisionBinding[]>, sceneNavigator: unknown, animations: StudioAnimation[], lastFiredRef: MutableRefObject<Map<string, number>>, onAnimationTrigger?: (targetAssetId: string, animationKey: string) => void, onSceneChange?: (sceneId: string, sceneName: string) => void): (viroTag: string, collidedPoint: [number, number, number], collidedNormal: [number, number, number]) => void;