@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,66 @@
1
+ import { StudioAsset } from "../types";
2
+ import { KeyedListeners, isDev } from "./utils";
3
+
4
+ /**
5
+ * Per-scene visibility store, keyed by scene asset placement id. Seeded from
6
+ * each asset's author-time `hidden_on_load` default at scene load; Set
7
+ * Visibility actions then flip the live value. Toggle reads the current value
8
+ * here, never the author default.
9
+ *
10
+ * Listeners are per-asset (unlike the global variable store) so a Set
11
+ * Visibility on one object repaints only that node, not the whole scene. Node
12
+ * factories subscribe to their own asset id.
13
+ */
14
+ export class StudioVisibilityStore {
15
+ private visible = new Map<string, boolean>();
16
+ private listeners = new KeyedListeners();
17
+
18
+ /** Current visibility; defaults to visible for assets never seeded/set. */
19
+ isVisible(assetId: string): boolean {
20
+ return this.visible.get(assetId) ?? true;
21
+ }
22
+
23
+ /** Subscribe to changes for one asset; returns an unsubscribe fn. */
24
+ subscribe(assetId: string, listener: () => void): () => void {
25
+ return this.listeners.subscribe(assetId, listener);
26
+ }
27
+
28
+ /** Initialise-if-absent from author-time defaults (idempotent, strict-mode safe). */
29
+ seed(assets: StudioAsset[]): void {
30
+ for (const asset of assets) {
31
+ if (!asset?.id) continue;
32
+ if (this.visible.has(asset.id)) continue;
33
+ this.visible.set(asset.id, !asset.hidden_on_load);
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Apply a Set Visibility action. TOGGLE flips the live value; VISIBLE/HIDDEN
39
+ * set it absolutely. No-op writes don't wake subscribers.
40
+ */
41
+ apply(assetId: string, state: "VISIBLE" | "HIDDEN" | "TOGGLE"): void {
42
+ const next =
43
+ state === "TOGGLE" ? !this.isVisible(assetId) : state === "VISIBLE";
44
+ if (this.visible.get(assetId) === next) return;
45
+ this.visible.set(assetId, next);
46
+ if (isDev()) {
47
+ console.log(`[Studio] Visibility "${assetId}" =`, next);
48
+ }
49
+ this.listeners.notify(assetId);
50
+ }
51
+
52
+ /**
53
+ * Re-initialise for a new scene: replace all values from author-time
54
+ * defaults, THEN notify every subscribed asset so mounted nodes re-read.
55
+ * Order matters — notifying before re-seeding would briefly surface the
56
+ * visible default for an asset that starts hidden.
57
+ */
58
+ reseed(assets: StudioAsset[]): void {
59
+ this.visible.clear();
60
+ for (const asset of assets) {
61
+ if (!asset?.id) continue;
62
+ this.visible.set(asset.id, !asset.hidden_on_load);
63
+ }
64
+ this.listeners.notifyAll();
65
+ }
66
+ }
@@ -14,5 +14,6 @@ export type {
14
14
  StudioSceneFunction,
15
15
  StudioSceneMeta,
16
16
  StudioSceneResponse,
17
+ StudioSceneVariable,
17
18
  ViroAnimationProp,
18
19
  } from "./types";
@@ -24,13 +24,159 @@ export interface StudioProjectMeta {
24
24
  occlusion_mode: "NONE" | "PEOPLEONLY" | "DEPTHBASED";
25
25
  }
26
26
 
27
+ /**
28
+ * One ordered step of a Sequence: an ACTION (runs a function), a WAIT (timed
29
+ * pause), or a STOP (explicit terminal — the run halts here).
30
+ */
31
+ export interface StudioSequenceStep {
32
+ id: string;
33
+ step_order: number;
34
+ step_type: "ACTION" | "WAIT" | "STOP";
35
+ duration_ms: number | null; // set for WAIT
36
+ function_id: string | null; // set for ACTION
37
+ function: StudioSceneFunction | null; // resolved child for ACTION; null for WAIT/STOP
38
+ // false (default) = wait for this step's effect to finish before the next
39
+ // step; true = fire-and-forget. Ignored for instant/terminal kinds.
40
+ advance_immediately: boolean;
41
+ }
42
+
43
+ export interface StudioSequence {
44
+ id: string;
45
+ name: string | null;
46
+ steps: StudioSequenceStep[];
47
+ }
48
+
49
+ /** Scene-level variable declaration. Runtime state lives in StudioVariableStore. */
50
+ export interface StudioSceneVariable {
51
+ id: string;
52
+ name: string;
53
+ type: "BOOLEAN" | "NUMBER" | "STRING";
54
+ initial_value: boolean | number | string;
55
+ }
56
+
57
+ /**
58
+ * One ordered predicate arm of a BRANCH. Variable names/types are joined from
59
+ * project_variables so they follow renames. The arm is an owned, headless
60
+ * sequence run like a nested sequence.
61
+ */
62
+ export interface StudioBranchCondition {
63
+ id: string;
64
+ eval_order: number;
65
+ variable_id: string;
66
+ variable_name: string;
67
+ variable_type: "BOOLEAN" | "NUMBER" | "STRING";
68
+ comparison:
69
+ | "EQUALS"
70
+ | "NOT_EQUALS"
71
+ | "GREATER_THAN"
72
+ | "LESS_THAN"
73
+ | "GREATER_OR_EQUAL"
74
+ | "LESS_OR_EQUAL"
75
+ | "LIKE"
76
+ | "ILIKE";
77
+ compare_literal: boolean | number | string | null;
78
+ compare_variable_id: string | null;
79
+ compare_variable_name: string | null;
80
+ compare_variable_type: "BOOLEAN" | "NUMBER" | "STRING" | null;
81
+ sequence: StudioSequence;
82
+ }
83
+
84
+ /**
85
+ * BRANCH payload. Conditions are evaluated in eval_order (first match wins),
86
+ * falling through to the optional no-match arm. Arms are owned, headless
87
+ * sequences run like nested sequences.
88
+ */
89
+ export interface StudioSceneBranch {
90
+ id: string;
91
+ conditions: StudioBranchCondition[];
92
+ no_match_sequence: StudioSequence | null;
93
+ }
94
+
95
+ /**
96
+ * GROUP payload. Owns N lanes, each an owned headless sequence run like a
97
+ * nested sequence. Lanes run concurrently; the group completes when all lanes
98
+ * complete (barrier join).
99
+ */
100
+ export interface StudioSceneGroup {
101
+ id: string;
102
+ lanes: { lane_order: number; sequence: StudioSequence }[];
103
+ }
104
+
105
+ /** One response->variable binding of an API_REQUEST function. */
106
+ export interface StudioApiRequestBinding {
107
+ id: string;
108
+ source: "BODY" | "STATUS" | "OK" | "ERROR_MESSAGE";
109
+ selector: string | null;
110
+ variable_id: string;
111
+ variable_name: string;
112
+ variable_type: "BOOLEAN" | "NUMBER" | "STRING";
113
+ bind_order: number;
114
+ }
115
+
116
+ /**
117
+ * API_REQUEST payload. The HTTP call executes server-side through the
118
+ * platform egress proxy (the client only ever sends the function id plus
119
+ * current variable values); connection_id is an opaque server-side
120
+ * reference. Arms are owned, headless sequences run like nested sequences.
121
+ */
122
+ export interface StudioSceneApiRequest {
123
+ id: string;
124
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
125
+ url_template: string;
126
+ headers: { key: string; value_template: string }[];
127
+ body_template: string | null;
128
+ timeout_ms: number;
129
+ connection_id: string | null;
130
+ bindings: StudioApiRequestBinding[];
131
+ success_sequence: StudioSequence | null;
132
+ failure_sequence: StudioSequence | null;
133
+ }
134
+
135
+ /** Outcome envelope an API-request executor resolves with. */
136
+ export interface StudioApiRequestOutcome {
137
+ ok: boolean;
138
+ status: number | null;
139
+ body?: unknown;
140
+ error_code?: string | null;
141
+ error_message?: string | null;
142
+ }
143
+
144
+ /**
145
+ * Host-app transport for API_REQUEST functions: StudioGo calls the
146
+ * scene-api-request edge function with the user JWT; production Viro apps
147
+ * call it with their X-API-Key. Must resolve with an outcome (reject only on
148
+ * transport failure).
149
+ */
150
+ export type StudioApiRequestExecutor = (
151
+ functionId: string,
152
+ variables: Record<string, boolean | number | string>,
153
+ ) => Promise<StudioApiRequestOutcome>;
154
+
27
155
  export interface StudioSceneFunction {
28
156
  id: string;
29
157
  scene: string;
30
- function_type: "NAVIGATION" | "ALERT" | "ANIMATION";
158
+ function_type:
159
+ | "NAVIGATION"
160
+ | "ALERT"
161
+ | "ANIMATION"
162
+ | "SEQUENCE"
163
+ | "SET_VARIABLE"
164
+ | "BRANCH"
165
+ | "API_REQUEST"
166
+ | "SET_VISIBILITY"
167
+ | "SOUND"
168
+ | "GROUP";
31
169
  navigation: string | null;
32
170
  alert: string | null;
33
171
  animation: string | null;
172
+ sequence: string | null;
173
+ set_variable: string | null;
174
+ branch: string | null;
175
+ api_request: string | null;
176
+ set_visibility: string | null;
177
+ sound: string | null;
178
+ // Named group_fn because `group` is a reserved SQL keyword.
179
+ group_fn: string | null;
34
180
  scene_navigation: { id: string; navigate_to: string } | null;
35
181
  scene_alert: {
36
182
  id: string;
@@ -44,6 +190,33 @@ export interface StudioSceneFunction {
44
190
  delay_ms: number | null;
45
191
  properties: Record<string, unknown>;
46
192
  } | null;
193
+ scene_sequence: StudioSequence | null;
194
+ /** name/type are joined from the target scene_variables row, so they follow renames. */
195
+ scene_set_variable: {
196
+ id: string;
197
+ variable_id: string;
198
+ name: string;
199
+ type: "BOOLEAN" | "NUMBER" | "STRING";
200
+ expression: string;
201
+ } | null;
202
+ scene_branch: StudioSceneBranch | null;
203
+ scene_api_request: StudioSceneApiRequest | null;
204
+ scene_set_visibility: {
205
+ id: string;
206
+ target_asset_id: string;
207
+ state: "VISIBLE" | "HIDDEN" | "TOGGLE";
208
+ } | null;
209
+ scene_sound: {
210
+ id: string;
211
+ action: "PLAY" | "STOP";
212
+ audio_asset_id: string | null;
213
+ audio_url: string | null;
214
+ target_asset_id: string | null;
215
+ volume: number;
216
+ loop: boolean;
217
+ stop_other_sounds: boolean;
218
+ } | null;
219
+ scene_group: StudioSceneGroup | null;
47
220
  }
48
221
 
49
222
  export interface StudioAsset {
@@ -63,6 +236,8 @@ export interface StudioAsset {
63
236
  latitude: number | null;
64
237
  longitude: number | null;
65
238
  is_draggable: boolean;
239
+ /** Author-time "hidden on start"; the runtime seeds visibility from it. */
240
+ hidden_on_load: boolean | null;
66
241
  trigger_image_url: string | null;
67
242
  trigger_image_orientation: "Up" | "Down" | "Left" | "Right" | null;
68
243
  trigger_image_physical_width_m: number | null;
@@ -149,6 +324,8 @@ export interface StudioSceneResponse {
149
324
  collision_bindings: StudioCollisionBinding[];
150
325
  animations: StudioAnimation[];
151
326
  functions: StudioSceneFunction[];
327
+ /** Absent in responses from backends predating the Variables feature. */
328
+ variables?: StudioSceneVariable[];
152
329
  meta: { request_id: string };
153
330
  }
154
331
 
@@ -15,8 +15,43 @@ export function exitVRScene(): void {
15
15
  export type VRModuleOpenXRType = {
16
16
  recenterTracking?: (viewTag: number) => void;
17
17
  setPassthroughEnabled?: (viewTag: number, enabled: boolean) => void;
18
+ setPassthroughStyle?: (
19
+ viewTag: number,
20
+ opacity: number,
21
+ edgeR: number,
22
+ edgeG: number,
23
+ edgeB: number,
24
+ edgeA: number
25
+ ) => void;
18
26
  };
19
27
 
28
+ /** Options for {@link setPassthroughStyle}. All channels are normalized [0,1]. */
29
+ export type ViroPassthroughStyle = {
30
+ /** Texture opacity factor [0,1]. Default 1 (fully opaque passthrough). */
31
+ opacity?: number;
32
+ /** Edge-highlight colour [r,g,b,a]. Alpha 0 (default) disables the edge effect. */
33
+ edgeColor?: [number, number, number, number];
34
+ };
35
+
36
+ /**
37
+ * Style the Quest passthrough layer (XR_FB_passthrough). No-op off-Quest.
38
+ *
39
+ * ```tsx
40
+ * const viewTag = useVRViewTag();
41
+ * if (viewTag != null) {
42
+ * setPassthroughStyle(viewTag, { opacity: 0.8, edgeColor: [0, 1, 1, 1] });
43
+ * }
44
+ * ```
45
+ */
46
+ export function setPassthroughStyle(
47
+ viewTag: number,
48
+ style: ViroPassthroughStyle
49
+ ): void {
50
+ const opacity = style.opacity ?? 1;
51
+ const [r, g, b, a] = style.edgeColor ?? [0, 0, 0, 0];
52
+ VRModuleOpenXR?.setPassthroughStyle?.(viewTag, opacity, r, g, b, a);
53
+ }
54
+
20
55
  /**
21
56
  * Typed reference to the VRModuleOpenXR native module.
22
57
  * undefined when not running on Meta Quest (no-op calls are safe via optional chaining).
@@ -1 +1 @@
1
- export const VIRO_VERSION = "2.56.0";
1
+ export const VIRO_VERSION = "2.57.2";
@@ -170,6 +170,7 @@ export class ViroAnimatedImage extends ViroBase<Props> {
170
170
  nativeProps.style = [this.props.style];
171
171
  nativeProps.onHoverViro = this._onHover;
172
172
  nativeProps.onClickViro = this._onClickState;
173
+ nativeProps.onClick = undefined;
173
174
  nativeProps.onTouchViro = this._onTouch;
174
175
  nativeProps.onScrollViro = this._onScroll;
175
176
  nativeProps.onSwipeViro = this._onSwipe;
@@ -59,6 +59,7 @@ export class ViroFlexView extends ViroBase<Props> {
59
59
  nativeProps.transformBehaviors = transformBehaviors;
60
60
  nativeProps.onHoverViro = this._onHover;
61
61
  nativeProps.onClickViro = this._onClickState;
62
+ nativeProps.onClick = undefined;
62
63
  nativeProps.onTouchViro = this._onTouch;
63
64
  nativeProps.onScrollViro = this._onScroll;
64
65
  nativeProps.onSwipeViro = this._onSwipe;
@@ -142,6 +142,7 @@ export class ViroImage extends ViroBase<Props> {
142
142
  nativeProps.style = [this.props.style];
143
143
  nativeProps.onHoverViro = this._onHover;
144
144
  nativeProps.onClickViro = this._onClickState;
145
+ nativeProps.onClick = undefined;
145
146
  nativeProps.onTouchViro = this._onTouch;
146
147
  nativeProps.onScrollViro = this._onScroll;
147
148
  nativeProps.onSwipeViro = this._onSwipe;
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Copyright (c) 2026-present, ReactVision, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import * as React from "react";
10
+ import {
11
+ NativeSyntheticEvent,
12
+ requireNativeComponent,
13
+ ViewProps,
14
+ } from "react-native";
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Types
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export type ViroDetectorMode = "prompt-free" | "text" | "visual";
21
+
22
+ export type ViroDetectionBoundingBox = {
23
+ /** Left edge, normalized [0, 1] */
24
+ x: number;
25
+ /** Top edge, normalized [0, 1] */
26
+ y: number;
27
+ width: number;
28
+ height: number;
29
+ };
30
+
31
+ export type ViroDetectedObject = {
32
+ label: string;
33
+ confidence: number;
34
+ boundingBox: ViroDetectionBoundingBox;
35
+ /** 3D world position (metres), from raycasting the bbox centre. Present when `projectToWorld` is true (iOS only). */
36
+ worldPosition?: { x: number; y: number; z: number };
37
+ /**
38
+ * Bounding box in density-independent points (dp), aligned to the on-screen AR
39
+ * camera preview. Use directly as { left, top, width, height } in an
40
+ * absolute-positioned View. Present on iOS and Android.
41
+ */
42
+ screenBoundingBox?: { x: number; y: number; width: number; height: number };
43
+ };
44
+
45
+ export type ViroDetectionEvent = {
46
+ detections: ViroDetectedObject[];
47
+ };
48
+
49
+ export type ViroDetectorReadyEvent = Record<string, never>;
50
+
51
+ export type ViroDetectorErrorEvent = {
52
+ error: string;
53
+ };
54
+
55
+ type Props = ViewProps & {
56
+ /**
57
+ * The YOLOE model to run. Either a bundled model **name** (resolved natively as
58
+ * `<name>.onnx` in the app bundle / Android assets), or an absolute `/`-path or
59
+ * `file://` URL to an `.onnx` file. Defaults to "yoloe-26s".
60
+ */
61
+ model?: string;
62
+
63
+ /**
64
+ * Inference mode:
65
+ * - "prompt-free": YOLOE LRPC — detects 4,585 categories with no extra prompt.
66
+ * - "text": YOLOE RepRTA — detect only the classes listed in `categories`.
67
+ * - "visual": YOLOE SAVPE — detect objects similar to a reference crop
68
+ * (reference image API to be added in a future release).
69
+ * Defaults to "prompt-free".
70
+ */
71
+ mode?: ViroDetectorMode;
72
+
73
+ /**
74
+ * Text categories for "text" mode, e.g. ["chair", "person", "laptop"].
75
+ * Ignored in other modes.
76
+ */
77
+ categories?: string[];
78
+
79
+ /**
80
+ * Minimum confidence score [0, 1] for a detection to be reported.
81
+ * Defaults to 0.4.
82
+ */
83
+ confidenceThreshold?: number;
84
+
85
+ /**
86
+ * IoU threshold used by NMS post-processing.
87
+ * Defaults to 0.45.
88
+ */
89
+ iouThreshold?: number;
90
+
91
+ /**
92
+ * Maximum number of inference calls per second. The camera runs at the
93
+ * device's native frame rate; this throttle prevents the inference thread
94
+ * from saturating the CPU/NPU while the AR renderer runs in parallel.
95
+ * Defaults to 15.
96
+ */
97
+ maxFPS?: number;
98
+
99
+ /**
100
+ * Maximum number of detections reported per frame, kept as the top-N by
101
+ * confidence (after NMS). Lower this to reduce clutter, raise it to surface
102
+ * more objects.
103
+ * Defaults to 20.
104
+ */
105
+ maxDetections?: number;
106
+
107
+ /**
108
+ * When true, each detection includes a `worldPosition` {x, y, z} obtained by
109
+ * raycasting the bbox centre against the AR scene. iOS only (Android emits
110
+ * `screenBoundingBox` but not yet `worldPosition`).
111
+ * Defaults to true.
112
+ */
113
+ projectToWorld?: boolean;
114
+
115
+ /**
116
+ * Called every time the detector produces a new set of detections.
117
+ * May be called with an empty array if nothing is detected in a frame.
118
+ */
119
+ onDetection?: (event: ViroDetectionEvent) => void;
120
+
121
+ /**
122
+ * Called once the model has been loaded and the camera pipeline is running.
123
+ */
124
+ onReady?: (event: ViroDetectorReadyEvent) => void;
125
+
126
+ /**
127
+ * Called if the model fails to load or the camera cannot be opened.
128
+ */
129
+ onError?: (event: ViroDetectorErrorEvent) => void;
130
+ };
131
+
132
+ // ---------------------------------------------------------------------------
133
+ // Internal native event types
134
+ // ---------------------------------------------------------------------------
135
+
136
+ type NativeDetectionEvent = NativeSyntheticEvent<ViroDetectionEvent>;
137
+ type NativeReadyEvent = NativeSyntheticEvent<ViroDetectorReadyEvent>;
138
+ type NativeErrorEvent = NativeSyntheticEvent<ViroDetectorErrorEvent>;
139
+
140
+ // ---------------------------------------------------------------------------
141
+ // Component
142
+ // ---------------------------------------------------------------------------
143
+
144
+ const VRTObjectDetectorView = requireNativeComponent<any>("VRTObjectDetectorView");
145
+
146
+ /**
147
+ * ViroObjectDetector — on-device open-vocabulary object detection powered by YOLOE.
148
+ *
149
+ * Runs in AR and on Meta Quest:
150
+ * - **Phone AR**: shares the camera feed of the enclosing `ViroARSceneNavigator`
151
+ * (no separate camera session) and fires `onDetection` with labels, normalized
152
+ * bounding boxes, and an on-screen `screenBoundingBox` (dp) at up to `maxFPS`.
153
+ * - **Meta Quest 3 / 3S** (Horizon OS v74+): when mounted in a Quest MR scene
154
+ * (under `ViroXRSceneNavigator`), it captures the headset camera via the Meta
155
+ * Passthrough Camera API and runs the same YOLOE pipeline. Requires the
156
+ * `horizonos.permission.HEADSET_CAMERA` runtime permission. v1 emits `label` +
157
+ * normalized `boundingBox` only — `screenBoundingBox` and `worldPosition` are
158
+ * not provided on Quest yet (they need camera extrinsics).
159
+ *
160
+ * Mount it as a child or sibling of the scene navigator; it renders nothing itself,
161
+ * so give it `width: 0, height: 0`.
162
+ *
163
+ * @example
164
+ * ```tsx
165
+ * <ViroARSceneNavigator initialScene={{ scene: MyScene }} />
166
+ * <ViroObjectDetector
167
+ * style={{ position: "absolute", width: 0, height: 0 }}
168
+ * mode="prompt-free"
169
+ * confidenceThreshold={0.4}
170
+ * maxFPS={15}
171
+ * onDetection={({ detections }) => {
172
+ * detections.forEach(d => console.log(d.label, d.confidence, d.screenBoundingBox));
173
+ * }}
174
+ * />
175
+ * ```
176
+ */
177
+ export const ViroObjectDetector: React.FC<Props> = ({
178
+ model = "yoloe-26s",
179
+ mode = "prompt-free",
180
+ categories = [],
181
+ confidenceThreshold = 0.4,
182
+ iouThreshold = 0.45,
183
+ maxFPS = 15,
184
+ maxDetections = 20,
185
+ projectToWorld = true,
186
+ onDetection,
187
+ onReady,
188
+ onError,
189
+ style,
190
+ ...rest
191
+ }) => {
192
+ const handleDetection = React.useCallback(
193
+ (event: NativeDetectionEvent) => {
194
+ onDetection?.(event.nativeEvent);
195
+ },
196
+ [onDetection]
197
+ );
198
+
199
+ const handleReady = React.useCallback(
200
+ (event: NativeReadyEvent) => {
201
+ onReady?.(event.nativeEvent);
202
+ },
203
+ [onReady]
204
+ );
205
+
206
+ const handleError = React.useCallback(
207
+ (event: NativeErrorEvent) => {
208
+ onError?.(event.nativeEvent);
209
+ },
210
+ [onError]
211
+ );
212
+
213
+ return (
214
+ <VRTObjectDetectorView
215
+ {...rest}
216
+ style={style}
217
+ model={model}
218
+ mode={mode}
219
+ categories={categories}
220
+ confidenceThreshold={confidenceThreshold}
221
+ iouThreshold={iouThreshold}
222
+ maxFPS={maxFPS}
223
+ maxDetections={maxDetections}
224
+ projectToWorld={projectToWorld}
225
+ onDetectionViro={onDetection ? handleDetection : undefined}
226
+ onReadyViro={onReady ? handleReady : undefined}
227
+ onErrorViro={onError ? handleError : undefined}
228
+ />
229
+ );
230
+ };
@@ -261,6 +261,7 @@ export class ViroParticleEmitter extends React.Component<Props, State> {
261
261
  : undefined;
262
262
  // Create native props object.
263
263
  let nativeProps = Object.assign({} as any, this.props);
264
+ nativeProps.onClick = undefined;
264
265
  nativeProps.position = this.state.propsPositionState;
265
266
  nativeProps.onNativeTransformDelegateViro = transformDelegate;
266
267
  nativeProps.hasTransformDelegate =
@@ -344,6 +345,7 @@ export class ViroParticleEmitter extends React.Component<Props, State> {
344
345
  };
345
346
  nativeProps.particleAppearance.rotation = newAppearanceRotMod;
346
347
  }
348
+ nativeProps.onClick = undefined;
347
349
  nativeProps.ref = (component: ViroNativeRef) => {
348
350
  this._component = component;
349
351
  };
@@ -60,7 +60,7 @@ type Props = {
60
60
  * that texture partially over the entire surface.
61
61
  */
62
62
  uvCoordinates?: ViroUVCoordinate;
63
- arShadowReceiver?: boolean
63
+ arShadowReceiver?: boolean;
64
64
  style?: ViroStyle;
65
65
  };
66
66
 
@@ -104,6 +104,7 @@ export class ViroPolygon extends ViroBase<Props> {
104
104
  nativeProps.style = [this.props.style];
105
105
  nativeProps.onHoverViro = this._onHover;
106
106
  nativeProps.onClickViro = this._onClickState;
107
+ nativeProps.onClick = undefined;
107
108
  nativeProps.onTouchViro = this._onTouch;
108
109
  nativeProps.onScrollViro = this._onScroll;
109
110
  nativeProps.onSwipeViro = this._onSwipe;
@@ -18,7 +18,7 @@ import { checkMisnamedProps } from "./Utilities/ViroProps";
18
18
  import { ViroBase } from "./ViroBase";
19
19
 
20
20
  type Props = {
21
- arShadowReceiver?: boolean
21
+ arShadowReceiver?: boolean;
22
22
  uvCoordinates?: ViroUVCoordinate[];
23
23
  };
24
24
 
@@ -62,6 +62,7 @@ export class ViroQuad extends ViroBase<Props> {
62
62
  nativeProps.style = [this.props.style];
63
63
  nativeProps.onHoverViro = this._onHover;
64
64
  nativeProps.onClickViro = this._onClickState;
65
+ nativeProps.onClick = undefined;
65
66
  nativeProps.onTouchViro = this._onTouch;
66
67
  nativeProps.onScrollViro = this._onScroll;
67
68
  nativeProps.onSwipeViro = this._onSwipe;
@@ -65,6 +65,7 @@ export class ViroSurface extends ViroBase<Props> {
65
65
  nativeProps.style = [this.props.style];
66
66
  nativeProps.onHoverViro = this._onHover;
67
67
  nativeProps.onClickViro = this._onClickState;
68
+ nativeProps.onClick = undefined;
68
69
  nativeProps.onTouchViro = this._onTouch;
69
70
  nativeProps.onScrollViro = this._onScroll;
70
71
  nativeProps.onSwipeViro = this._onSwipe;
@@ -131,6 +131,7 @@ export class ViroVideo extends ViroBase<Props> {
131
131
  nativeProps.onUpdateTimeViro = this._onUpdateTime;
132
132
  nativeProps.onHoverViro = this._onHover;
133
133
  nativeProps.onClickViro = this._onClickState;
134
+ nativeProps.onClick = undefined;
134
135
  nativeProps.onTouchViro = this._onTouch;
135
136
  nativeProps.onScrollViro = this._onScroll;
136
137
  nativeProps.onSwipeViro = this._onSwipe;
@@ -59,6 +59,14 @@ type Props = ViewProps & {
59
59
  * On Quest, this scene is forwarded to VRActivity via VRQuestNavigatorBridge
60
60
  * rather than rendered inline, because OpenXR exclusive display requires the
61
61
  * VR intent category on the host Activity.
62
+ *
63
+ * The scene root may be either:
64
+ * - `ViroScene` → fully-virtual VR.
65
+ * - `ViroARScene` → mixed reality: passthrough is enabled automatically and
66
+ * real-time plane detection (XR_EXT_plane_detection, Quest 3 / 3S) drives
67
+ * `onAnchorFound` and `ViroARPlane`, exactly as on phone AR. A single
68
+ * AR-rooted `initialScene` therefore runs on phones (ARCore) and Quest (OpenXR)
69
+ * with no per-platform changes. See docs/QUEST_SETUP.md §7b.
62
70
  */
63
71
  vrInitialScene?: SceneFactory;
64
72
 
@@ -25,6 +25,6 @@ export declare class ViroARPlane extends ViroBase<Props> {
25
25
  _onAnchorFound: (event: NativeSyntheticEvent<ViroARAnchorFoundEvent>) => void;
26
26
  _onAnchorUpdated: (event: NativeSyntheticEvent<ViroARAnchorUpdatedEvent>) => void;
27
27
  _onAnchorRemoved: (_event: NativeSyntheticEvent<ViroARAnchorRemovedEvent>) => void;
28
- render(): React.JSX.Element | null;
28
+ render(): React.JSX.Element;
29
29
  }
30
30
  export {};