@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
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Last-resort cap on how long a step waits for a non-looping PLAY to finish. A
3
+ * clip that fails to load fires onError (released at once); a clip whose native
4
+ * finish/error event is dropped or never arrives would otherwise stall the walk
5
+ * forever, so the manager force-releases the waiter after this many ms.
6
+ * Generous on purpose: it must outlast any realistic single clip so it never
7
+ * cuts one short, only catches a genuine stall.
8
+ */
9
+ export declare const SOUND_WAIT_BACKSTOP_MS: number;
10
+ /** One actively-playing sound, keyed in the manager by its monotonic playId. */
11
+ export type StudioSoundEntry = {
12
+ playId: number;
13
+ audioAssetId: string;
14
+ url: string;
15
+ position?: [number, number, number];
16
+ volume: number;
17
+ loop: boolean;
18
+ };
19
+ /**
20
+ * Per-scene sound store. PLAY adds an entry under a fresh playId; STOP removes
21
+ * by audio asset id (null = all). The whole <StudioSounds> list re-renders on
22
+ * any change, so subscribers are GLOBAL like StudioVariableStore (not per-key).
23
+ */
24
+ export declare class StudioSoundManager {
25
+ private sounds;
26
+ private listeners;
27
+ private nextPlayId;
28
+ private finishCallbacks;
29
+ private finishTimers;
30
+ /** Subscribe to any add/remove; returns an unsubscribe fn. */
31
+ subscribe(listener: () => void): () => void;
32
+ getActive(): StudioSoundEntry[];
33
+ /** Pull and invoke the stored completion callback (if any) for a playId. */
34
+ private fire;
35
+ /**
36
+ * Adds a sound and returns its playId. onFinish (when given) resolves a step
37
+ * waiting on a non-looping PLAY; it fires on natural finish or early stop.
38
+ */
39
+ play(entry: {
40
+ audioAssetId: string;
41
+ url: string;
42
+ position?: [number, number, number];
43
+ volume: number;
44
+ loop: boolean;
45
+ stopOthers: boolean;
46
+ }, onFinish?: () => void): number;
47
+ /** null = stop all sounds; otherwise stop every entry for this audio asset. */
48
+ stop(audioAssetId: string | null): void;
49
+ /** Drop one entry; onFinish calls this for non-looping sounds. */
50
+ remove(playId: number): void;
51
+ reset(): void;
52
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StudioSoundManager = exports.SOUND_WAIT_BACKSTOP_MS = void 0;
4
+ const utils_1 = require("./utils");
5
+ /**
6
+ * Last-resort cap on how long a step waits for a non-looping PLAY to finish. A
7
+ * clip that fails to load fires onError (released at once); a clip whose native
8
+ * finish/error event is dropped or never arrives would otherwise stall the walk
9
+ * forever, so the manager force-releases the waiter after this many ms.
10
+ * Generous on purpose: it must outlast any realistic single clip so it never
11
+ * cuts one short, only catches a genuine stall.
12
+ */
13
+ exports.SOUND_WAIT_BACKSTOP_MS = 5 * 60 * 1000;
14
+ /**
15
+ * Per-scene sound store. PLAY adds an entry under a fresh playId; STOP removes
16
+ * by audio asset id (null = all). The whole <StudioSounds> list re-renders on
17
+ * any change, so subscribers are GLOBAL like StudioVariableStore (not per-key).
18
+ */
19
+ class StudioSoundManager {
20
+ sounds = new Map();
21
+ listeners = new utils_1.GlobalListeners();
22
+ nextPlayId = 1;
23
+ // Per-playId completion callback for a step waiting on a non-looping PLAY.
24
+ // Fired on natural finish (via remove) AND when the sound is cut short by a
25
+ // stop/stopOthers/reset, so a waited-on sound never stalls the walk.
26
+ finishCallbacks = new Map();
27
+ // Backstop timer per waited playId; cleared whenever its callback fires so a
28
+ // sound whose native finish/error event never arrives can't stall the walk.
29
+ finishTimers = new Map();
30
+ /** Subscribe to any add/remove; returns an unsubscribe fn. */
31
+ subscribe(listener) {
32
+ return this.listeners.subscribe(listener);
33
+ }
34
+ getActive() {
35
+ return [...this.sounds.values()];
36
+ }
37
+ /** Pull and invoke the stored completion callback (if any) for a playId. */
38
+ fire(playId) {
39
+ const timer = this.finishTimers.get(playId);
40
+ if (timer !== undefined) {
41
+ clearTimeout(timer);
42
+ this.finishTimers.delete(playId);
43
+ }
44
+ const cb = this.finishCallbacks.get(playId);
45
+ if (!cb)
46
+ return;
47
+ this.finishCallbacks.delete(playId);
48
+ cb();
49
+ }
50
+ /**
51
+ * Adds a sound and returns its playId. onFinish (when given) resolves a step
52
+ * waiting on a non-looping PLAY; it fires on natural finish or early stop.
53
+ */
54
+ play(entry, onFinish) {
55
+ // stopOthers clears the live list; fire any pending waiters for the cleared
56
+ // entries so a displaced waited-on sound resolves instead of stalling.
57
+ if (entry.stopOthers) {
58
+ const cleared = [...this.sounds.keys()];
59
+ this.sounds.clear();
60
+ for (const id of cleared)
61
+ this.fire(id);
62
+ }
63
+ const playId = this.nextPlayId++;
64
+ this.sounds.set(playId, {
65
+ playId,
66
+ audioAssetId: entry.audioAssetId,
67
+ url: entry.url,
68
+ position: entry.position,
69
+ volume: entry.volume,
70
+ loop: entry.loop,
71
+ });
72
+ if (onFinish) {
73
+ this.finishCallbacks.set(playId, onFinish);
74
+ // Non-looping waited sound: arm the stall backstop. A looping sound is
75
+ // never awaited, so it gets no timer.
76
+ if (!entry.loop) {
77
+ this.finishTimers.set(playId, setTimeout(() => this.remove(playId), exports.SOUND_WAIT_BACKSTOP_MS));
78
+ }
79
+ }
80
+ if ((0, utils_1.isDev)()) {
81
+ console.log(`[Studio] Sound play "${entry.audioAssetId}" (#${playId})`);
82
+ }
83
+ this.listeners.notify();
84
+ return playId;
85
+ }
86
+ /** null = stop all sounds; otherwise stop every entry for this audio asset. */
87
+ stop(audioAssetId) {
88
+ const removed = [];
89
+ for (const [id, e] of this.sounds) {
90
+ if (audioAssetId === null || e.audioAssetId === audioAssetId) {
91
+ this.sounds.delete(id);
92
+ removed.push(id);
93
+ }
94
+ }
95
+ this.listeners.notify();
96
+ // Resolve any waiters cut short by the stop.
97
+ for (const id of removed)
98
+ this.fire(id);
99
+ }
100
+ /** Drop one entry; onFinish calls this for non-looping sounds. */
101
+ remove(playId) {
102
+ if (this.sounds.delete(playId))
103
+ this.listeners.notify();
104
+ // Fire the stored callback whether or not the entry was still present
105
+ // (natural finish path: resolves the waiting step).
106
+ this.fire(playId);
107
+ }
108
+ reset() {
109
+ const pending = [...this.finishCallbacks.keys()];
110
+ this.sounds.clear();
111
+ this.listeners.notify();
112
+ // Belt-and-suspenders: the runtime's generation guard no-ops these after a
113
+ // scene-change cancelAll, but fire so nothing leaks on bare resets.
114
+ for (const id of pending)
115
+ this.fire(id);
116
+ }
117
+ }
118
+ exports.StudioSoundManager = StudioSoundManager;
@@ -0,0 +1,16 @@
1
+ /** True only in a dev build; gates verbose runtime logging in the Studio stores. */
2
+ export declare const isDev: () => boolean;
3
+ /** A set of subscribers notified together (variable + sound stores). */
4
+ export declare class GlobalListeners {
5
+ private listeners;
6
+ subscribe(listener: () => void): () => void;
7
+ notify(): void;
8
+ }
9
+ /** Subscribers partitioned by key; notify(key) wakes only that key's set
10
+ * (the visibility store's per-asset variant). */
11
+ export declare class KeyedListeners {
12
+ private listeners;
13
+ subscribe(key: string, listener: () => void): () => void;
14
+ notify(key: string): void;
15
+ notifyAll(): void;
16
+ }
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ // Small shared helpers for the Studio runtime domain.
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.KeyedListeners = exports.GlobalListeners = exports.isDev = void 0;
5
+ /** True only in a dev build; gates verbose runtime logging in the Studio stores. */
6
+ const isDev = () => typeof __DEV__ !== "undefined" && __DEV__;
7
+ exports.isDev = isDev;
8
+ /** A set of subscribers notified together (variable + sound stores). */
9
+ class GlobalListeners {
10
+ listeners = new Set();
11
+ subscribe(listener) {
12
+ this.listeners.add(listener);
13
+ return () => {
14
+ this.listeners.delete(listener);
15
+ };
16
+ }
17
+ notify() {
18
+ // Snapshot first: a listener may (un)subscribe during its own callback.
19
+ [...this.listeners].forEach((fn) => fn());
20
+ }
21
+ }
22
+ exports.GlobalListeners = GlobalListeners;
23
+ /** Subscribers partitioned by key; notify(key) wakes only that key's set
24
+ * (the visibility store's per-asset variant). */
25
+ class KeyedListeners {
26
+ listeners = new Map();
27
+ subscribe(key, listener) {
28
+ let set = this.listeners.get(key);
29
+ if (!set) {
30
+ set = new Set();
31
+ this.listeners.set(key, set);
32
+ }
33
+ set.add(listener);
34
+ return () => {
35
+ set?.delete(listener);
36
+ };
37
+ }
38
+ notify(key) {
39
+ // Snapshot first: a listener may (un)subscribe during its own callback.
40
+ const set = this.listeners.get(key);
41
+ if (set)
42
+ [...set].forEach((fn) => fn());
43
+ }
44
+ notifyAll() {
45
+ this.listeners.forEach((set) => [...set].forEach((fn) => fn()));
46
+ }
47
+ }
48
+ exports.KeyedListeners = KeyedListeners;
@@ -0,0 +1,21 @@
1
+ import { StudioSceneVariable } from "../types";
2
+ import { StudioVariableValue } from "./expressionEvaluator";
3
+ /**
4
+ * Per-session variable store. One instance is owned by the navigator and
5
+ * survives scene pushes; it resets only when the AR/VR session ends.
6
+ *
7
+ * Identity is by NAME: scenes declaring the same name share one value, and
8
+ * seed() only initialises names not already present (initialize-if-absent),
9
+ * so values carry across scene transitions.
10
+ */
11
+ export declare class StudioVariableStore {
12
+ private values;
13
+ private listeners;
14
+ get(name: string): StudioVariableValue | undefined;
15
+ /** Subscribe to value changes (set/reset); returns an unsubscribe fn. */
16
+ subscribe(listener: () => void): () => void;
17
+ set(name: string, value: StudioVariableValue): void;
18
+ seed(declarations: StudioSceneVariable[]): void;
19
+ reset(): void;
20
+ snapshot(): Record<string, StudioVariableValue>;
21
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StudioVariableStore = void 0;
4
+ const expressionEvaluator_1 = require("./expressionEvaluator");
5
+ const utils_1 = require("./utils");
6
+ /**
7
+ * Per-session variable store. One instance is owned by the navigator and
8
+ * survives scene pushes; it resets only when the AR/VR session ends.
9
+ *
10
+ * Identity is by NAME: scenes declaring the same name share one value, and
11
+ * seed() only initialises names not already present (initialize-if-absent),
12
+ * so values carry across scene transitions.
13
+ */
14
+ class StudioVariableStore {
15
+ values = new Map();
16
+ listeners = new utils_1.GlobalListeners();
17
+ get(name) {
18
+ return this.values.get(name);
19
+ }
20
+ /** Subscribe to value changes (set/reset); returns an unsubscribe fn. */
21
+ subscribe(listener) {
22
+ return this.listeners.subscribe(listener);
23
+ }
24
+ set(name, value) {
25
+ // Values are primitives; skip the no-op write so unchanged values don't
26
+ // log or wake subscribers.
27
+ if (Object.is(this.values.get(name), value))
28
+ return;
29
+ this.values.set(name, value);
30
+ if ((0, utils_1.isDev)()) {
31
+ console.log(`[Studio] Variable "${name}" =`, value);
32
+ }
33
+ this.listeners.notify();
34
+ }
35
+ seed(declarations) {
36
+ for (const decl of declarations) {
37
+ if (!decl?.name)
38
+ continue;
39
+ if (!(0, expressionEvaluator_1.valueMatchesType)(decl.initial_value, decl.type)) {
40
+ console.warn(`[Studio] Variable "${decl.name}": initial value does not match type ${decl.type}; skipping seed.`);
41
+ continue;
42
+ }
43
+ const existing = this.values.get(decl.name);
44
+ if (existing !== undefined) {
45
+ if (!(0, expressionEvaluator_1.valueMatchesType)(existing, decl.type)) {
46
+ console.warn(`[Studio] Variable "${decl.name}" already holds a ${typeof existing} this session, but this scene declares ${decl.type}; keeping the existing value.`);
47
+ }
48
+ continue;
49
+ }
50
+ this.values.set(decl.name, decl.initial_value);
51
+ }
52
+ }
53
+ reset() {
54
+ this.values.clear();
55
+ this.listeners.notify();
56
+ }
57
+ snapshot() {
58
+ const out = {};
59
+ this.values.forEach((value, name) => {
60
+ out[name] = value;
61
+ });
62
+ return out;
63
+ }
64
+ }
65
+ exports.StudioVariableStore = StudioVariableStore;
@@ -1,5 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { StudioAnimation, StudioAsset, StudioSceneMeta, ViroAnimationProp } from "../types";
3
+ import { SequenceRuntimeContext } from "./sceneNavigationHandler";
3
4
  type SceneNavigator = any;
4
5
  export type NodeConfig = {
5
6
  position: [number, number, number];
@@ -16,13 +17,7 @@ export type NodeConfig = {
16
17
  onClick?: () => void;
17
18
  animation?: ViroAnimationProp;
18
19
  };
19
- /**
20
- * Derives the transform config for an asset.
21
- * Clamps Z to -2 for non-trigger assets to guarantee visibility.
22
- */
23
- export declare function createNodeConfig(asset: StudioAsset, sceneNavigator: SceneNavigator | undefined, animations: StudioAnimation[], scene: StudioSceneMeta | null, onAnimationTrigger?: (targetAssetId: string, animKey: string) => void, animationStates?: Record<string, ViroAnimationProp>, onSceneChange?: (sceneId: string, sceneName: string) => void): NodeConfig;
24
- /**
25
- * Creates the appropriate Viro component for a StudioAsset.
26
- */
27
- export declare function createNode(asset: StudioAsset, sceneNavigator: SceneNavigator | undefined, animations: StudioAnimation[], scene: StudioSceneMeta | null, onAnimationTrigger?: (targetAssetId: string, animKey: string) => void, animationStates?: Record<string, ViroAnimationProp>, onAssetLoaded?: (id: string) => void, onCollision?: (viroTag: string, collidedPoint: [number, number, number], collidedNormal: [number, number, number]) => void, onSceneChange?: (sceneId: string, sceneName: string) => void): React.ReactElement | null;
20
+ /** Clamps Z to -2 for non-trigger assets to guarantee visibility. */
21
+ export declare function createNodeConfig(asset: StudioAsset, sceneNavigator: SceneNavigator | undefined, animations: StudioAnimation[], scene: StudioSceneMeta | null, onAnimationTrigger?: (targetAssetId: string, animKey: string) => void, animationStates?: Record<string, ViroAnimationProp>, onSceneChange?: (sceneId: string, sceneName: string) => void, runtimeCtx?: SequenceRuntimeContext): NodeConfig;
22
+ export declare function createNode(asset: StudioAsset, sceneNavigator: SceneNavigator | undefined, animations: StudioAnimation[], scene: StudioSceneMeta | null, onAnimationTrigger?: (targetAssetId: string, animKey: string) => void, animationStates?: Record<string, ViroAnimationProp>, onAssetLoaded?: (id: string) => void, onCollision?: (viroTag: string, collidedPoint: [number, number, number], collidedNormal: [number, number, number]) => void, onSceneChange?: (sceneId: string, sceneName: string) => void, runtimeCtx?: SequenceRuntimeContext): React.ReactElement | null;
28
23
  export {};
@@ -42,14 +42,12 @@ const ViroImage_1 = require("../../ViroImage");
42
42
  const ViroText_1 = require("../../ViroText");
43
43
  const ViroVideo_1 = require("../../ViroVideo");
44
44
  const sceneNavigationHandler_1 = require("./sceneNavigationHandler");
45
+ const apiRequestHelpers_1 = require("./apiRequestHelpers");
45
46
  const materialConfig_1 = require("./materialConfig");
46
47
  const dragConfiguration_1 = require("./dragConfiguration");
47
48
  const physicsConfig_1 = require("./physicsConfig");
48
- /**
49
- * Derives the transform config for an asset.
50
- * Clamps Z to -2 for non-trigger assets to guarantee visibility.
51
- */
52
- function createNodeConfig(asset, sceneNavigator, animations, scene, onAnimationTrigger, animationStates, onSceneChange) {
49
+ /** Clamps Z to -2 for non-trigger assets to guarantee visibility. */
50
+ function createNodeConfig(asset, sceneNavigator, animations, scene, onAnimationTrigger, animationStates, onSceneChange, runtimeCtx) {
53
51
  const hasTriggerImage = !!asset.trigger_image_url;
54
52
  let posZ = asset.position_z ?? -2;
55
53
  if (!hasTriggerImage && posZ > -0.5) {
@@ -89,13 +87,25 @@ function createNodeConfig(asset, sceneNavigator, animations, scene, onAnimationT
89
87
  dragPlane = dragConfiguration_1.DragConfiguration.getDragPlane(scene?.plane_direction ?? "Horizontal", position);
90
88
  }
91
89
  const parsedPhysics = (0, physicsConfig_1.parsePhysicsBodyConfig)(asset.physics_config);
92
- const physicsBody = parsedPhysics ? (0, physicsConfig_1.buildViroPhysicsBody)(parsedPhysics) : undefined;
90
+ const physicsBody = parsedPhysics
91
+ ? (0, physicsConfig_1.buildViroPhysicsBody)(parsedPhysics)
92
+ : undefined;
93
93
  const viroTag = parsedPhysics ? asset.id : undefined;
94
- const onClick = createOnClickHandler(asset, sceneNavigator, animations, onAnimationTrigger, onSceneChange);
94
+ const onClick = createOnClickHandler(asset, sceneNavigator, animations, onAnimationTrigger, onSceneChange, runtimeCtx);
95
95
  const animation = animationStates?.[asset.id];
96
- return { position, rotation, scale, dragType, dragPlane, physicsBody, viroTag, onClick, animation };
96
+ return {
97
+ position,
98
+ rotation,
99
+ scale,
100
+ dragType,
101
+ dragPlane,
102
+ physicsBody,
103
+ viroTag,
104
+ onClick,
105
+ animation,
106
+ };
97
107
  }
98
- function createOnClickHandler(asset, sceneNavigator, animations, onAnimationTrigger, onSceneChange) {
108
+ function createOnClickHandler(asset, sceneNavigator, animations, onAnimationTrigger, onSceneChange, runtimeCtx) {
99
109
  const fn = asset.scene_function;
100
110
  if (!fn)
101
111
  return undefined;
@@ -111,13 +121,11 @@ function createOnClickHandler(asset, sceneNavigator, animations, onAnimationTrig
111
121
  console.warn(`[Studio] Asset "${asset.name}" has ANIMATION but no animation data`);
112
122
  return undefined;
113
123
  }
114
- return () => (0, sceneNavigationHandler_1.executeFunctionWithRelations)(fn, sceneNavigator, animations, onAnimationTrigger, 0, onSceneChange);
124
+ return () => (0, sceneNavigationHandler_1.executeFunctionWithRelations)(fn, sceneNavigator, animations, onAnimationTrigger, 0, onSceneChange, runtimeCtx);
115
125
  }
116
- /** Resolves asset type from asset_type_name. */
117
126
  function resolveType(asset) {
118
127
  return asset.asset_type_name ?? null;
119
128
  }
120
- /** Infers 3D model format from file extension. */
121
129
  function inferModelType(url) {
122
130
  const ext = url.toLowerCase().split(".").pop();
123
131
  if (ext === "gltf")
@@ -143,11 +151,15 @@ function create3DObject(asset, config, onAssetLoaded, onCollision) {
143
151
  ]
144
152
  : config.scale;
145
153
  const hasMaterialConfig = (0, materialConfig_1.parseMaterialConfig)(asset.material_config) !== null;
146
- const shaderOverrides = hasMaterialConfig ? [(0, materialConfig_1.studioMaterialName)(asset.id)] : undefined;
154
+ const shaderOverrides = hasMaterialConfig
155
+ ? [(0, materialConfig_1.studioMaterialName)(asset.id)]
156
+ : undefined;
147
157
  return (<Viro3DObject_1.Viro3DObject key={asset.id} source={{ uri: asset.file_url }} position={config.position} rotation={config.rotation} scale={scale} type={modelType} dragType={config.dragType} dragPlane={config.dragPlane} animation={config.animation} onClick={config.onClick} renderingOrder={react_native_1.Platform.OS === "android" ? 1 : 0} onLoadEnd={() => onAssetLoaded?.(asset.id)} onError={(e) => console.error(`[Studio] 3D model "${asset.name}" error:`, e)}
148
158
  // Viro derives native canDrag from `onDrag != undefined`; without this prop
149
159
  // the drag recognizer is never attached, even when dragType is set.
150
- {...(config.dragType ? { onDrag: () => { } } : {})} {...(shaderOverrides ? { shaderOverrides } : {})} {...(config.physicsBody ? { physicsBody: config.physicsBody, viroTag: config.viroTag } : {})} {...(onCollision ? { onCollision: onCollision } : {})}/>);
160
+ {...(config.dragType ? { onDrag: () => { } } : {})} {...(shaderOverrides ? { shaderOverrides } : {})} {...(config.physicsBody
161
+ ? { physicsBody: config.physicsBody, viroTag: config.viroTag }
162
+ : {})} {...(onCollision ? { onCollision: onCollision } : {})}/>);
151
163
  }
152
164
  function createImage(asset, config, onAssetLoaded) {
153
165
  if (!asset.file_url) {
@@ -156,13 +168,51 @@ function createImage(asset, config, onAssetLoaded) {
156
168
  }
157
169
  return (<ViroImage_1.ViroImage key={asset.id} source={{ uri: asset.file_url }} position={config.position} rotation={config.rotation} scale={config.scale} dragType={config.dragType} animation={config.animation} onClick={config.onClick} onLoadEnd={() => onAssetLoaded?.(asset.id)} onError={(e) => console.error(`[Studio] Image "${asset.name}" error:`, e)} {...(config.dragType ? { onDrag: () => { } } : {})}/>);
158
170
  }
159
- function createText(asset, config) {
160
- return (<ViroText_1.ViroText key={asset.id} text={asset.name ?? ""} position={config.position} rotation={config.rotation} scale={config.scale} dragType={config.dragType} animation={config.animation} onClick={config.onClick} style={{
171
+ /**
172
+ * TEXT node whose content is a {{variable}} template (the asset name). It
173
+ * re-interpolates and repaints whenever a referenced variable changes (and only
174
+ * subscribes when the template actually has placeholders). Resolution is
175
+ * fail-soft: unknown names stay literal.
176
+ */
177
+ const VariableText = ({ asset, config, store, visible }) => {
178
+ const template = asset.name ?? "";
179
+ const compute = () => store
180
+ ? (0, apiRequestHelpers_1.interpolateDisplayTemplate)(template, (n) => store.get(n))
181
+ : template;
182
+ const [text, setText] = React.useState(compute);
183
+ React.useEffect(() => {
184
+ if (!store || (0, apiRequestHelpers_1.extractPlaceholders)(template).length === 0)
185
+ return;
186
+ // Resync any write that landed between first render and subscribe.
187
+ setText(compute());
188
+ return store.subscribe(() => setText(compute()));
189
+ // eslint-disable-next-line react-hooks/exhaustive-deps
190
+ }, [store, template]);
191
+ return (<ViroText_1.ViroText text={text} position={config.position} rotation={config.rotation} scale={config.scale} dragType={config.dragType} animation={config.animation} onClick={config.onClick} {...(visible === undefined ? {} : { visible })} style={{
161
192
  fontFamily: "Arial",
162
193
  fontSize: 20,
163
194
  color: "#FFFFFF",
164
195
  textAlign: "center",
165
196
  }} {...(config.dragType ? { onDrag: () => { } } : {})}/>);
197
+ };
198
+ /**
199
+ * Wraps a created node and drives its `visible` prop from the per-scene
200
+ * visibility store, subscribing to its own asset so a Set Visibility action
201
+ * repaints only this node. Without a store, the node stays visible.
202
+ */
203
+ const VisibleNode = ({ assetId, store, children }) => {
204
+ const [visible, setVisible] = React.useState(() => store?.isVisible(assetId) ?? true);
205
+ React.useEffect(() => {
206
+ if (!store)
207
+ return;
208
+ // Resync any write that landed between first render and subscribe.
209
+ setVisible(store.isVisible(assetId));
210
+ return store.subscribe(assetId, () => setVisible(store.isVisible(assetId)));
211
+ }, [store, assetId]);
212
+ return React.cloneElement(children, { visible });
213
+ };
214
+ function createText(asset, config, store) {
215
+ return (<VariableText key={asset.id} asset={asset} config={config} store={store}/>);
166
216
  }
167
217
  function createVideo(asset, config) {
168
218
  if (!asset.file_url) {
@@ -171,23 +221,32 @@ function createVideo(asset, config) {
171
221
  }
172
222
  return (<ViroVideo_1.ViroVideo key={asset.id} source={{ uri: asset.file_url }} position={config.position} rotation={config.rotation} scale={config.scale} dragType={config.dragType} animation={config.animation} onClick={config.onClick} loop={true} muted={false} onError={(e) => console.error(`[Studio] Video "${asset.name}" error:`, e)} {...(config.dragType ? { onDrag: () => { } } : {})}/>);
173
223
  }
174
- /**
175
- * Creates the appropriate Viro component for a StudioAsset.
176
- */
177
- function createNode(asset, sceneNavigator, animations, scene, onAnimationTrigger, animationStates, onAssetLoaded, onCollision, onSceneChange) {
224
+ function createNode(asset, sceneNavigator, animations, scene, onAnimationTrigger, animationStates, onAssetLoaded, onCollision, onSceneChange, runtimeCtx) {
178
225
  const type = resolveType(asset);
179
- const config = createNodeConfig(asset, sceneNavigator, animations, scene, onAnimationTrigger, animationStates, onSceneChange);
226
+ const config = createNodeConfig(asset, sceneNavigator, animations, scene, onAnimationTrigger, animationStates, onSceneChange, runtimeCtx);
227
+ let node;
180
228
  switch (type) {
181
229
  case "3D-MODEL":
182
- return create3DObject(asset, config, onAssetLoaded, onCollision);
230
+ node = create3DObject(asset, config, onAssetLoaded, onCollision);
231
+ break;
183
232
  case "IMAGE":
184
- return createImage(asset, config, onAssetLoaded);
233
+ node = createImage(asset, config, onAssetLoaded);
234
+ break;
185
235
  case "TEXT":
186
- return createText(asset, config);
236
+ node = createText(asset, config, runtimeCtx?.variableStore);
237
+ break;
187
238
  case "VIDEO":
188
- return createVideo(asset, config);
239
+ node = createVideo(asset, config);
240
+ break;
189
241
  default:
190
242
  console.warn(`[Studio] Unknown asset type "${type}" for "${asset.name}"`);
191
243
  return null;
192
244
  }
245
+ if (!node)
246
+ return null;
247
+ // Drive show/hide/toggle from the visibility store (Set Visibility actions);
248
+ // seeded from the asset's author-time hidden_on_load default.
249
+ return (<VisibleNode key={asset.id} assetId={asset.id} store={runtimeCtx?.visibilityStore}>
250
+ {node}
251
+ </VisibleNode>);
193
252
  }
@@ -0,0 +1,33 @@
1
+ import { StudioAsset } from "../types";
2
+ /**
3
+ * Per-scene visibility store, keyed by scene asset placement id. Seeded from
4
+ * each asset's author-time `hidden_on_load` default at scene load; Set
5
+ * Visibility actions then flip the live value. Toggle reads the current value
6
+ * here, never the author default.
7
+ *
8
+ * Listeners are per-asset (unlike the global variable store) so a Set
9
+ * Visibility on one object repaints only that node, not the whole scene. Node
10
+ * factories subscribe to their own asset id.
11
+ */
12
+ export declare class StudioVisibilityStore {
13
+ private visible;
14
+ private listeners;
15
+ /** Current visibility; defaults to visible for assets never seeded/set. */
16
+ isVisible(assetId: string): boolean;
17
+ /** Subscribe to changes for one asset; returns an unsubscribe fn. */
18
+ subscribe(assetId: string, listener: () => void): () => void;
19
+ /** Initialise-if-absent from author-time defaults (idempotent, strict-mode safe). */
20
+ seed(assets: StudioAsset[]): void;
21
+ /**
22
+ * Apply a Set Visibility action. TOGGLE flips the live value; VISIBLE/HIDDEN
23
+ * set it absolutely. No-op writes don't wake subscribers.
24
+ */
25
+ apply(assetId: string, state: "VISIBLE" | "HIDDEN" | "TOGGLE"): void;
26
+ /**
27
+ * Re-initialise for a new scene: replace all values from author-time
28
+ * defaults, THEN notify every subscribed asset so mounted nodes re-read.
29
+ * Order matters — notifying before re-seeding would briefly surface the
30
+ * visible default for an asset that starts hidden.
31
+ */
32
+ reseed(assets: StudioAsset[]): void;
33
+ }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StudioVisibilityStore = void 0;
4
+ const utils_1 = require("./utils");
5
+ /**
6
+ * Per-scene visibility store, keyed by scene asset placement id. Seeded from
7
+ * each asset's author-time `hidden_on_load` default at scene load; Set
8
+ * Visibility actions then flip the live value. Toggle reads the current value
9
+ * here, never the author default.
10
+ *
11
+ * Listeners are per-asset (unlike the global variable store) so a Set
12
+ * Visibility on one object repaints only that node, not the whole scene. Node
13
+ * factories subscribe to their own asset id.
14
+ */
15
+ class StudioVisibilityStore {
16
+ visible = new Map();
17
+ listeners = new utils_1.KeyedListeners();
18
+ /** Current visibility; defaults to visible for assets never seeded/set. */
19
+ isVisible(assetId) {
20
+ return this.visible.get(assetId) ?? true;
21
+ }
22
+ /** Subscribe to changes for one asset; returns an unsubscribe fn. */
23
+ subscribe(assetId, listener) {
24
+ return this.listeners.subscribe(assetId, listener);
25
+ }
26
+ /** Initialise-if-absent from author-time defaults (idempotent, strict-mode safe). */
27
+ seed(assets) {
28
+ for (const asset of assets) {
29
+ if (!asset?.id)
30
+ continue;
31
+ if (this.visible.has(asset.id))
32
+ continue;
33
+ this.visible.set(asset.id, !asset.hidden_on_load);
34
+ }
35
+ }
36
+ /**
37
+ * Apply a Set Visibility action. TOGGLE flips the live value; VISIBLE/HIDDEN
38
+ * set it absolutely. No-op writes don't wake subscribers.
39
+ */
40
+ apply(assetId, state) {
41
+ const next = state === "TOGGLE" ? !this.isVisible(assetId) : state === "VISIBLE";
42
+ if (this.visible.get(assetId) === next)
43
+ return;
44
+ this.visible.set(assetId, next);
45
+ if ((0, utils_1.isDev)()) {
46
+ console.log(`[Studio] Visibility "${assetId}" =`, next);
47
+ }
48
+ this.listeners.notify(assetId);
49
+ }
50
+ /**
51
+ * Re-initialise for a new scene: replace all values from author-time
52
+ * defaults, THEN notify every subscribed asset so mounted nodes re-read.
53
+ * Order matters — notifying before re-seeding would briefly surface the
54
+ * visible default for an asset that starts hidden.
55
+ */
56
+ reseed(assets) {
57
+ this.visible.clear();
58
+ for (const asset of assets) {
59
+ if (!asset?.id)
60
+ continue;
61
+ this.visible.set(asset.id, !asset.hidden_on_load);
62
+ }
63
+ this.listeners.notifyAll();
64
+ }
65
+ }
66
+ exports.StudioVisibilityStore = StudioVisibilityStore;
@@ -1,3 +1,3 @@
1
1
  export { StudioARScene } from "./StudioARScene";
2
2
  export { StudioSceneNavigator } from "./StudioSceneNavigator";
3
- export type { StudioAnimation, StudioAsset, StudioCollisionBinding, StudioProjectApiResponse, StudioProjectAsset, StudioProjectMeta, StudioProjectOpeningScene, StudioProjectOverview, StudioProjectSceneSummary, StudioSceneCreatedBy, StudioSceneFunction, StudioSceneMeta, StudioSceneResponse, ViroAnimationProp, } from "./types";
3
+ export type { StudioAnimation, StudioAsset, StudioCollisionBinding, StudioProjectApiResponse, StudioProjectAsset, StudioProjectMeta, StudioProjectOpeningScene, StudioProjectOverview, StudioProjectSceneSummary, StudioSceneCreatedBy, StudioSceneFunction, StudioSceneMeta, StudioSceneResponse, StudioSceneVariable, ViroAnimationProp, } from "./types";