@reactvision/react-viro 2.55.0 → 2.57.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.
- package/README.md +27 -0
- package/android/react_viro/react_viro-release.aar +0 -0
- package/android/viro_renderer/viro_renderer-release.aar +0 -0
- package/components/AR/ViroARScene.tsx +17 -0
- package/components/AR/ViroARSceneNavigator.tsx +33 -0
- package/components/Utilities/StreamingAudioManager.ts +73 -0
- package/components/Utilities/ViroVersion.ts +1 -1
- package/components/ViroCameraTexture.tsx +230 -0
- package/components/ViroGameLoop.tsx +74 -0
- package/components/ViroGameLoopUtils.ts +52 -0
- package/components/ViroObjectDetector.tsx +222 -0
- package/components/ViroVirtualButton.tsx +101 -0
- package/components/ViroVirtualJoystick.tsx +126 -0
- package/components/hooks/useGameLoop.ts +78 -0
- package/dist/components/AR/ViroARScene.d.ts +12 -0
- package/dist/components/AR/ViroARScene.js +8 -1
- package/dist/components/AR/ViroARSceneNavigator.d.ts +30 -0
- package/dist/components/Utilities/StreamingAudioManager.d.ts +22 -0
- package/dist/components/Utilities/StreamingAudioManager.js +45 -0
- package/dist/components/Utilities/ViroVersion.d.ts +1 -1
- package/dist/components/Utilities/ViroVersion.js +1 -1
- package/dist/components/ViroCameraTexture.d.ts +137 -0
- package/dist/components/ViroCameraTexture.js +170 -0
- package/dist/components/ViroGameLoop.d.ts +31 -0
- package/dist/components/ViroGameLoop.js +31 -0
- package/dist/components/ViroGameLoopUtils.d.ts +26 -0
- package/dist/components/ViroGameLoopUtils.js +44 -0
- package/dist/components/ViroObjectDetector.d.ts +138 -0
- package/dist/components/ViroObjectDetector.js +85 -0
- package/dist/components/ViroVirtualButton.d.ts +76 -0
- package/dist/components/ViroVirtualButton.js +62 -0
- package/dist/components/ViroVirtualJoystick.d.ts +85 -0
- package/dist/components/ViroVirtualJoystick.js +72 -0
- package/dist/components/hooks/useGameLoop.d.ts +34 -0
- package/dist/components/hooks/useGameLoop.js +63 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +20 -2
- package/dist/plugins/withViro.js +2 -15
- package/dist/plugins/withViroAndroid.js +52 -15
- package/dist/plugins/withViroIos.js +0 -6
- package/index.ts +21 -0
- package/ios/ViroReact.podspec +25 -6
- package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreCoreMLSemanticsResources.bundle/Info.plist +0 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/ARCoreResources.bundle/Info.plist +0 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARDepthMesh.h +9 -1
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARFrame.h +33 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARFrameiOS.h +26 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARScene.h +15 -1
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSceneDelegateiOS.h +8 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSession.h +7 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSessioniOS.h +18 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARWorldMesh.h +111 -8
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROAudioPlayer.h +19 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROAudioPlayerStreamiOS.h +95 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROCameraTextureiOS.h +17 -1
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRODriver.h +20 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRODynamicGeometry.h +170 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRODynamicMeshNode.h +87 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROGLTFLoader.h +5 -1
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROGameLoopListener.h +125 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROInputState.h +133 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROMonocularDepthEstimator.h +73 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROObjectDetector.h +134 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROPCMRingBuffer.h +143 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROPencil.h +9 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRORenderContext.h +4 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRORenderer.h +1 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROVertexBuffer.h +102 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROViewAR.h +9 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROVirtualControllerRegistry.h +106 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Info.plist +0 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Shaders.dat +1 -1
- package/ios/dist/ViroRenderer/ViroKit.framework/ViroKit +0 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/constant_fsh.glsl +1 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/constant_fsh_adreno.glsl +1 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/standard_fsh.glsl +1 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/standard_vsh.glsl +4 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/standard_vsh_adreno.glsl +4 -0
- package/ios/dist/ViroRendererVisionOS/ViroKit.podspec +27 -0
- package/ios/dist/include/VRTARScene.h +1 -0
- package/ios/dist/include/VRTARSceneNavigator.h +3 -0
- package/ios/dist/include/VRTCameraTexture.h +62 -0
- package/ios/dist/include/VRTCameraTextureManager.h +30 -0
- package/ios/dist/include/VRTCameraTextureModule.h +38 -0
- package/ios/dist/include/VRTGameLoopView.h +19 -0
- package/ios/dist/include/VRTGameLoopViewManager.h +8 -0
- package/ios/dist/include/VRTManagedAnimation.h +67 -0
- package/ios/dist/include/VRTStreamingAudioModule.h +21 -0
- package/ios/dist/include/VRTVirtualButtonView.h +47 -0
- package/ios/dist/include/VRTVirtualButtonViewManager.h +21 -0
- package/ios/dist/include/VRTVirtualJoystickView.h +59 -0
- package/ios/dist/include/VRTVirtualJoystickViewManager.h +26 -0
- package/ios/dist/lib/libViroReact.a +0 -0
- package/package.json +17 -20
package/README.md
CHANGED
|
@@ -45,6 +45,14 @@ For Expo projects, the easiest way to start is to clone the official starter kit
|
|
|
45
45
|
|
|
46
46
|
For step-by-step setup instructions, including platform-specific permissions and build configuration, see the full installation guide in the docs: <https://viro-community.readme.io/docs/installation-instructions>
|
|
47
47
|
|
|
48
|
+
## MCP Server
|
|
49
|
+
|
|
50
|
+
If you're building AR, VR or XR apps with ViroReact, our MCP server is the fastest way to get moving. Think of it as a living guide to ViroReact, always current, that plugs straight into the tools you already use.
|
|
51
|
+
|
|
52
|
+
Connect it to Claude, Codex, Cursor or any other coding agent, and your agent instantly understands how to work with ViroReact's cross-platform rendering. No hunting through docs, no guesswork. (Our documentation is still a great companion, but the MCP is the most up to date source there is.)
|
|
53
|
+
|
|
54
|
+
[MCP Setup Instructions](https://viro-community.readme.io/docs/viroreact-mcp-server)
|
|
55
|
+
|
|
48
56
|
## Features
|
|
49
57
|
|
|
50
58
|
ViroReact ships with a complete spatial computing toolkit out of the box — no third-party plugins, no paid add-ons.
|
|
@@ -84,6 +92,25 @@ You can get a Studio account, which includes Platform access, for free at <https
|
|
|
84
92
|
- Cloud Anchors guide: <https://viro-community.readme.io/docs/cloud-anchors>
|
|
85
93
|
- Geospatial Anchors guide: <https://viro-community.readme.io/docs/geospatial-anchors>
|
|
86
94
|
|
|
95
|
+
## Studio
|
|
96
|
+
|
|
97
|
+
[ReactVision Studio](https://studio.reactvision.xyz) is our browser-based visual scene editor for AR and VR. Build scenes visually in Studio, then embed them inside your own ViroReact app with a single component. Designers and developers can iterate on a scene in Studio and have it update in production without a new app build.
|
|
98
|
+
|
|
99
|
+
**Build scenes visually**
|
|
100
|
+
|
|
101
|
+
- **Placement** — drag-and-drop 3D objects, images, video, text, and primitives into a scene with visual gizmos for position, rotation, and scale. No JSX required to lay out a scene.
|
|
102
|
+
- **AI asset generation** — generate 3D models from a text prompt or reference image directly inside the editor, powered by ReactVision Platform.
|
|
103
|
+
- **Animations** — author keyframe and property animations on a timeline, chain them into sequences, and trigger them from interactions or scene events.
|
|
104
|
+
- **Physics** — assign dynamic, static, or kinematic bodies to objects, configure mass, friction, and restitution, and wire up collision callbacks — the same physics engine ViroReact ships with, exposed visually.
|
|
105
|
+
|
|
106
|
+
**Embed Studio scenes in your app**
|
|
107
|
+
|
|
108
|
+
Drop the `StudioSceneNavigator` component into your ViroReact app, point it at a Studio scene, and your app picks up whatever your team is editing in Studio without a new native build.
|
|
109
|
+
|
|
110
|
+
**Getting started**
|
|
111
|
+
|
|
112
|
+
Full walkthrough — including Studio setup, exporting a scene, and wiring `StudioSceneNavigator` into a React Native app — is in our blog: <https://updates.reactvision.xyz/how-to-build-your-first-ar-vr-app-with-studio-and-react-native-f2421ecce9ae>
|
|
113
|
+
|
|
87
114
|
## Documentation
|
|
88
115
|
|
|
89
116
|
- Full API reference and guides: <https://viro-community.readme.io/docs/overview>
|
|
Binary file
|
|
Binary file
|
|
@@ -72,6 +72,14 @@ type Props = ViroCommonProps & {
|
|
|
72
72
|
) => void;
|
|
73
73
|
onPlatformUpdate?: (platformInfoViro: ViroPlatformInfo) => void;
|
|
74
74
|
onAmbientLightUpdate?: (update: ViroAmbientLightInfo) => void;
|
|
75
|
+
/**
|
|
76
|
+
* Fired once when depth data (LiDAR or monocular depth estimation) first becomes
|
|
77
|
+
* available for the session — i.e. hit tests can now return DepthPoints. Useful for
|
|
78
|
+
* hiding an "initializing depth…" state instead of acting on early, inaccurate
|
|
79
|
+
* feature points. On devices using monocular depth estimation, this can take a few
|
|
80
|
+
* seconds after the AR screen appears while the model warms up.
|
|
81
|
+
*/
|
|
82
|
+
onDepthReady?: () => void;
|
|
75
83
|
/**
|
|
76
84
|
* Describes the acoustic properties of the room around the user
|
|
77
85
|
*/
|
|
@@ -237,6 +245,13 @@ export class ViroARScene extends ViroBase<Props> {
|
|
|
237
245
|
this.props.onAmbientLightUpdate(event.nativeEvent.ambientLightInfo);
|
|
238
246
|
};
|
|
239
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Fired once when depth data first becomes available for hit testing.
|
|
250
|
+
*/
|
|
251
|
+
_onDepthReady = (_event: NativeSyntheticEvent<{}>) => {
|
|
252
|
+
this.props.onDepthReady && this.props.onDepthReady();
|
|
253
|
+
};
|
|
254
|
+
|
|
240
255
|
_onAnchorFound = (event: NativeSyntheticEvent<ViroARAnchorFoundEvent>) => {
|
|
241
256
|
// TODO: this is in a different format than the other onAnchorFound methods
|
|
242
257
|
this.props.onAnchorFound &&
|
|
@@ -527,6 +542,7 @@ export class ViroARScene extends ViroBase<Props> {
|
|
|
527
542
|
onPlatformUpdateViro={this._onPlatformUpdate}
|
|
528
543
|
onTrackingUpdatedViro={this._onTrackingUpdated}
|
|
529
544
|
onAmbientLightUpdateViro={this._onAmbientLightUpdate}
|
|
545
|
+
onDepthReadyViro={this._onDepthReady}
|
|
530
546
|
onAnchorFoundViro={this._onAnchorFound}
|
|
531
547
|
onAnchorUpdatedViro={this._onAnchorUpdated}
|
|
532
548
|
onAnchorRemovedViro={this._onAnchorRemoved}
|
|
@@ -573,6 +589,7 @@ var VRTARScene = requireNativeComponent<any>(
|
|
|
573
589
|
onTrackingInitializedViro: true,
|
|
574
590
|
onTrackingUpdatedViro: true,
|
|
575
591
|
onAmbientLightUpdateViro: true,
|
|
592
|
+
onDepthReadyViro: true,
|
|
576
593
|
onAnchorFoundViro: true,
|
|
577
594
|
onAnchorUpdatedViro: true,
|
|
578
595
|
onAnchorRemovedViro: true,
|
|
@@ -184,6 +184,39 @@ type Props = ViewProps & {
|
|
|
184
184
|
*/
|
|
185
185
|
preferMonocularDepth?: boolean;
|
|
186
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Calibration scale applied to monocular depth values before use in occlusion.
|
|
189
|
+
* 1.0 (default) = no change. Use < 1.0 if the model overestimates distances
|
|
190
|
+
* (virtual objects visible through real surfaces). Use > 1.0 if it underestimates.
|
|
191
|
+
* Typical tuning range: 0.7 – 1.3.
|
|
192
|
+
*
|
|
193
|
+
* @default 1.0
|
|
194
|
+
* @platform ios
|
|
195
|
+
*/
|
|
196
|
+
monocularDepthScale?: number;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Maximum inference rate for monocular depth (default: 5).
|
|
200
|
+
* Lower values reduce device heat. Thermal state automatically
|
|
201
|
+
* overrides this downward: Fair→3fps, Serious→2fps, Critical→stopped.
|
|
202
|
+
* 3fps is barely perceptible for occlusion; 5fps is very smooth.
|
|
203
|
+
*
|
|
204
|
+
* @default 5
|
|
205
|
+
* @platform ios
|
|
206
|
+
*/
|
|
207
|
+
monocularDepthTargetFPS?: number;
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Use the front (selfie) camera as the AR session background.
|
|
211
|
+
* On iOS uses ARFaceTrackingConfiguration (requires TrueDepth camera, iPhone X+).
|
|
212
|
+
* On Android uses ARCore Augmented Faces mode (front camera).
|
|
213
|
+
* World tracking, plane detection, and LiDAR are unavailable in this mode.
|
|
214
|
+
*
|
|
215
|
+
* @default false
|
|
216
|
+
* @platform ios, android
|
|
217
|
+
*/
|
|
218
|
+
frontCameraEnabled?: boolean;
|
|
219
|
+
|
|
187
220
|
/**
|
|
188
221
|
* Cloud and geospatial anchor provider.
|
|
189
222
|
* Set to `"reactvision"` (default) for the ReactVision backend,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { NativeModules } from "react-native";
|
|
2
|
+
|
|
3
|
+
interface StreamingAudioNative {
|
|
4
|
+
create(playerId: string): void;
|
|
5
|
+
beginStreaming(playerId: string, sampleRate: number, channels: number): void;
|
|
6
|
+
play(playerId: string): void;
|
|
7
|
+
pause(playerId: string): void;
|
|
8
|
+
setVolume(playerId: string, volume: number): void;
|
|
9
|
+
setMuted(playerId: string, muted: boolean): void;
|
|
10
|
+
/**
|
|
11
|
+
* Push interleaved float32 PCM samples encoded as base64 (little-endian IEEE 754).
|
|
12
|
+
*
|
|
13
|
+
* JS encoding:
|
|
14
|
+
* const bytes = new Uint8Array(float32Array.buffer);
|
|
15
|
+
* const b64 = btoa(String.fromCharCode(...bytes));
|
|
16
|
+
*/
|
|
17
|
+
pushSamples(playerId: string, base64Samples: string): void;
|
|
18
|
+
destroy(playerId: string): void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// RCT_EXPORT_MODULE() in newer RN (0.76+) keeps the "Module" suffix in NativeModules.
|
|
22
|
+
const native = (NativeModules.VRTStreamingAudioModule ?? NativeModules.VRTStreamingAudio) as
|
|
23
|
+
| StreamingAudioNative
|
|
24
|
+
| undefined;
|
|
25
|
+
|
|
26
|
+
function noop(..._args: unknown[]): void {}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* StreamingAudioManager — imperative API for streaming PCM audio.
|
|
30
|
+
*
|
|
31
|
+
* Typical TTS flow:
|
|
32
|
+
* StreamingAudioManager.create('tts');
|
|
33
|
+
* StreamingAudioManager.beginStreaming('tts', 24000, 1);
|
|
34
|
+
* StreamingAudioManager.play('tts');
|
|
35
|
+
* // on each TTS audio chunk:
|
|
36
|
+
* StreamingAudioManager.pushSamples('tts', base64FloatPCM);
|
|
37
|
+
* // when done:
|
|
38
|
+
* StreamingAudioManager.destroy('tts');
|
|
39
|
+
*/
|
|
40
|
+
export const StreamingAudioManager = {
|
|
41
|
+
create: native
|
|
42
|
+
? (id: string) => native.create(id)
|
|
43
|
+
: (noop as (id: string) => void),
|
|
44
|
+
|
|
45
|
+
beginStreaming: native
|
|
46
|
+
? (id: string, sampleRate: number, channels: number) =>
|
|
47
|
+
native.beginStreaming(id, sampleRate, channels)
|
|
48
|
+
: (noop as (id: string, sr: number, ch: number) => void),
|
|
49
|
+
|
|
50
|
+
play: native
|
|
51
|
+
? (id: string) => native.play(id)
|
|
52
|
+
: (noop as (id: string) => void),
|
|
53
|
+
|
|
54
|
+
pause: native
|
|
55
|
+
? (id: string) => native.pause(id)
|
|
56
|
+
: (noop as (id: string) => void),
|
|
57
|
+
|
|
58
|
+
setVolume: native
|
|
59
|
+
? (id: string, volume: number) => native.setVolume(id, volume)
|
|
60
|
+
: (noop as (id: string, v: number) => void),
|
|
61
|
+
|
|
62
|
+
setMuted: native
|
|
63
|
+
? (id: string, muted: boolean) => native.setMuted(id, muted)
|
|
64
|
+
: (noop as (id: string, m: boolean) => void),
|
|
65
|
+
|
|
66
|
+
pushSamples: native
|
|
67
|
+
? (id: string, base64: string) => native.pushSamples(id, base64)
|
|
68
|
+
: (noop as (id: string, b64: string) => void),
|
|
69
|
+
|
|
70
|
+
destroy: native
|
|
71
|
+
? (id: string) => native.destroy(id)
|
|
72
|
+
: (noop as (id: string) => void),
|
|
73
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const VIRO_VERSION = "2.
|
|
1
|
+
export const VIRO_VERSION = "2.57.0";
|
|
@@ -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
|
+
findNodeHandle,
|
|
12
|
+
NativeModules,
|
|
13
|
+
NativeSyntheticEvent,
|
|
14
|
+
requireNativeComponent,
|
|
15
|
+
ViewProps,
|
|
16
|
+
} from "react-native";
|
|
17
|
+
import { ViroErrorEvent } from "./Types/ViroEvents";
|
|
18
|
+
import { ViroNativeRef } from "./Types/ViroUtils";
|
|
19
|
+
|
|
20
|
+
const { VRTCameraTextureModule } = NativeModules;
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Types
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
export type ViroCameraPosition = "front" | "back";
|
|
27
|
+
|
|
28
|
+
export type ViroCameraReadyEvent = Record<string, never>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Result returned by capturePhoto / startRecording / stopRecording.
|
|
32
|
+
* Mirrors the response shape from both iOS and Android NativeModules.
|
|
33
|
+
*/
|
|
34
|
+
export type ViroCaptureResult =
|
|
35
|
+
| { success: true; url: string }
|
|
36
|
+
| { success: false; error: string };
|
|
37
|
+
|
|
38
|
+
export type ViroCapturePhotoOptions = {
|
|
39
|
+
/** Absolute file path for the JPEG. Defaults to a cache-dir path. */
|
|
40
|
+
outputPath?: string;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type ViroCaptureVideoOptions = {
|
|
44
|
+
/** Absolute file path for the MP4. Defaults to a cache-dir path. */
|
|
45
|
+
outputPath?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
type Props = ViewProps & {
|
|
49
|
+
/**
|
|
50
|
+
* Name of the material (created via ViroMaterials.createMaterials) to bind
|
|
51
|
+
* the live camera feed to as its diffuse texture. Unlike ViroMaterialVideo,
|
|
52
|
+
* this component creates the camera texture internally and sets it on the
|
|
53
|
+
* named material — the material only needs a lightingModel defined.
|
|
54
|
+
*/
|
|
55
|
+
material: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Which device camera to use. Front camera feed is automatically mirrored
|
|
59
|
+
* for a natural selfie effect. Defaults to "front".
|
|
60
|
+
*/
|
|
61
|
+
cameraPosition?: ViroCameraPosition;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Whether camera capture is paused. When true the last captured frame is
|
|
65
|
+
* held and battery usage drops to near zero. Defaults to false.
|
|
66
|
+
*/
|
|
67
|
+
paused?: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Called once when the first camera frame is available and the texture is
|
|
71
|
+
* ready to be rendered. Fires from the native AVFoundation / Camera2 callback,
|
|
72
|
+
* not immediately on mount.
|
|
73
|
+
*/
|
|
74
|
+
onCameraReady?: () => void;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Called when the camera fails to initialise (permission denied, hardware
|
|
78
|
+
* error, invalid material name, etc.).
|
|
79
|
+
*/
|
|
80
|
+
onError?: (event: NativeSyntheticEvent<ViroErrorEvent>) => void;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Component
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* ViroCameraTexture binds a live device camera feed to a named material texture.
|
|
89
|
+
*
|
|
90
|
+
* The component creates the camera texture internally and sets it on the material,
|
|
91
|
+
* so the material only needs a lightingModel. The texture is updated every frame.
|
|
92
|
+
*
|
|
93
|
+
* **Usage:**
|
|
94
|
+
* ```tsx
|
|
95
|
+
* import { ViroMaterials, ViroQuad, ViroCameraTexture } from '@reactvision/react-viro';
|
|
96
|
+
*
|
|
97
|
+
* ViroMaterials.createMaterials({
|
|
98
|
+
* selfieMat: { lightingModel: 'Constant' },
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* <ViroARScene>
|
|
102
|
+
* <ViroQuad position={[0, 0, -2]} width={1.6} height={2.4} materials={["selfieMat"]} />
|
|
103
|
+
* <ViroCameraTexture
|
|
104
|
+
* material="selfieMat"
|
|
105
|
+
* cameraPosition="front"
|
|
106
|
+
* onCameraReady={() => console.log('Camera ready')}
|
|
107
|
+
* onError={(e) => console.error(e.nativeEvent.error)}
|
|
108
|
+
* />
|
|
109
|
+
* </ViroARScene>
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
export class ViroCameraTexture extends React.Component<Props> {
|
|
113
|
+
_component: ViroNativeRef = null;
|
|
114
|
+
|
|
115
|
+
_onCameraReady = () => {
|
|
116
|
+
this.props.onCameraReady && this.props.onCameraReady();
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
_onError = (event: NativeSyntheticEvent<ViroErrorEvent>) => {
|
|
120
|
+
this.props.onError && this.props.onError(event);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Capture API
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Capture a single JPEG still from the camera feed.
|
|
129
|
+
*
|
|
130
|
+
* @param options.outputPath Absolute path for the output JPEG.
|
|
131
|
+
* Omit to let the native layer choose a default
|
|
132
|
+
* cache-directory path.
|
|
133
|
+
* @returns Promise resolving to `{ success: true, url }` on success or
|
|
134
|
+
* `{ success: false, error }` on failure.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* const result = await cameraRef.current?.capturePhoto();
|
|
139
|
+
* if (result?.success) console.log('Saved to', result.url);
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
async capturePhoto(
|
|
143
|
+
options: ViroCapturePhotoOptions = {}
|
|
144
|
+
): Promise<ViroCaptureResult> {
|
|
145
|
+
const tag = findNodeHandle(this._component);
|
|
146
|
+
if (tag == null) {
|
|
147
|
+
return { success: false, error: "ViroCameraTexture is not mounted" };
|
|
148
|
+
}
|
|
149
|
+
return VRTCameraTextureModule.capturePhoto(
|
|
150
|
+
tag,
|
|
151
|
+
options.outputPath ?? null
|
|
152
|
+
) as Promise<ViroCaptureResult>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Start recording the camera feed to an MP4 file.
|
|
157
|
+
*
|
|
158
|
+
* The promise resolves once the recording session has successfully started.
|
|
159
|
+
* Call {@link stopRecording} to finalise the file.
|
|
160
|
+
*
|
|
161
|
+
* @param options.outputPath Absolute path for the output MP4.
|
|
162
|
+
* Omit to let the native layer choose a default.
|
|
163
|
+
* @returns Promise resolving to `{ success: true, url }` (the path that
|
|
164
|
+
* will be written) or `{ success: false, error }`.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* await cameraRef.current?.startRecording();
|
|
169
|
+
* // … some time later …
|
|
170
|
+
* const result = await cameraRef.current?.stopRecording();
|
|
171
|
+
* if (result?.success) console.log('Video saved to', result.url);
|
|
172
|
+
* ```
|
|
173
|
+
*/
|
|
174
|
+
async startRecording(
|
|
175
|
+
options: ViroCaptureVideoOptions = {}
|
|
176
|
+
): Promise<ViroCaptureResult> {
|
|
177
|
+
const tag = findNodeHandle(this._component);
|
|
178
|
+
if (tag == null) {
|
|
179
|
+
return { success: false, error: "ViroCameraTexture is not mounted" };
|
|
180
|
+
}
|
|
181
|
+
return VRTCameraTextureModule.startRecording(
|
|
182
|
+
tag,
|
|
183
|
+
options.outputPath ?? null
|
|
184
|
+
) as Promise<ViroCaptureResult>;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Stop an in-progress recording and finalise the MP4 file.
|
|
189
|
+
*
|
|
190
|
+
* @returns Promise resolving to `{ success: true, url }` with the path of
|
|
191
|
+
* the written file, or `{ success: false, error }`.
|
|
192
|
+
*/
|
|
193
|
+
async stopRecording(): Promise<ViroCaptureResult> {
|
|
194
|
+
const tag = findNodeHandle(this._component);
|
|
195
|
+
if (tag == null) {
|
|
196
|
+
return { success: false, error: "ViroCameraTexture is not mounted" };
|
|
197
|
+
}
|
|
198
|
+
return VRTCameraTextureModule.stopRecording(
|
|
199
|
+
tag
|
|
200
|
+
) as Promise<ViroCaptureResult>;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
render() {
|
|
204
|
+
const nativeProps = Object.assign({} as any, this.props);
|
|
205
|
+
nativeProps.cameraPosition = this.props.cameraPosition ?? "front";
|
|
206
|
+
nativeProps.paused = this.props.paused ?? false;
|
|
207
|
+
nativeProps.onCameraReadyViro = this._onCameraReady;
|
|
208
|
+
nativeProps.onErrorViro = this._onError;
|
|
209
|
+
nativeProps.ref = (component: ViroNativeRef) => {
|
|
210
|
+
this._component = component;
|
|
211
|
+
};
|
|
212
|
+
return <VRTCameraTexture {...nativeProps} />;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// ---------------------------------------------------------------------------
|
|
217
|
+
// Native component binding
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
|
|
220
|
+
const VRTCameraTexture = requireNativeComponent<any>(
|
|
221
|
+
"VRTCameraTexture",
|
|
222
|
+
// @ts-ignore
|
|
223
|
+
ViroCameraTexture,
|
|
224
|
+
{
|
|
225
|
+
nativeOnly: {
|
|
226
|
+
onCameraReadyViro: true,
|
|
227
|
+
onErrorViro: true,
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
);
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ViroGameLoop.tsx — headless component that fires per-frame JS callbacks.
|
|
3
|
+
*
|
|
4
|
+
* Mount anywhere inside a ViroARScene or ViroScene to start the loop.
|
|
5
|
+
* The loop automatically stops when the component unmounts.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* <ViroGameLoop onUpdate={(dt, elapsed) => { ... }} />
|
|
9
|
+
* <ViroGameLoop fixedHz={30} onFixedUpdate={(dt) => { ... }} />
|
|
10
|
+
*
|
|
11
|
+
* Copyright © 2026 ReactVision. All rights reserved.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import React from "react";
|
|
15
|
+
import { requireNativeComponent, ViewStyle } from "react-native";
|
|
16
|
+
|
|
17
|
+
const VRTGameLoopView = requireNativeComponent<ViroGameLoopNativeProps>(
|
|
18
|
+
"VRTGameLoopView"
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
// ── Types ─────────────────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
export type ViroGameLoopUpdateEvent = {
|
|
24
|
+
dt: number; // delta time in seconds since last frame
|
|
25
|
+
elapsed: number; // total elapsed seconds since component mounted
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type ViroGameLoopFixedEvent = {
|
|
29
|
+
dt: number; // fixed delta (1 / fixedHz)
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export type ViroGameLoopProps = {
|
|
33
|
+
/** Called every rendered frame. dt = seconds since last frame. */
|
|
34
|
+
onUpdate?: (event: ViroGameLoopUpdateEvent) => void;
|
|
35
|
+
/** Called after physics + rendering each frame (useLateUpdate equivalent). */
|
|
36
|
+
onLateUpdate?: (event: ViroGameLoopUpdateEvent) => void;
|
|
37
|
+
/** Fixed simulation frequency in Hz. When set, onFixedUpdate fires at this rate. */
|
|
38
|
+
fixedHz?: number;
|
|
39
|
+
/** Called at a fixed rate determined by fixedHz. */
|
|
40
|
+
onFixedUpdate?: (event: ViroGameLoopFixedEvent) => void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type ViroGameLoopNativeProps = {
|
|
44
|
+
onUpdate?: (e: { nativeEvent: ViroGameLoopUpdateEvent }) => void;
|
|
45
|
+
onLateUpdate?: (e: { nativeEvent: ViroGameLoopUpdateEvent }) => void;
|
|
46
|
+
onFixedUpdate?: (e: { nativeEvent: ViroGameLoopFixedEvent }) => void;
|
|
47
|
+
fixedHz?: number;
|
|
48
|
+
style?: ViewStyle;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// ── Component ─────────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
export function ViroGameLoop({
|
|
54
|
+
onUpdate,
|
|
55
|
+
onLateUpdate,
|
|
56
|
+
onFixedUpdate,
|
|
57
|
+
fixedHz,
|
|
58
|
+
}: ViroGameLoopProps) {
|
|
59
|
+
// Native sends dt/elapsed as strings to avoid Fabric conversions.h type-check spam.
|
|
60
|
+
const parse = (e: any) => ({
|
|
61
|
+
dt: parseFloat(e.nativeEvent.dt),
|
|
62
|
+
elapsed: parseFloat(e.nativeEvent.elapsed ?? "0"),
|
|
63
|
+
});
|
|
64
|
+
const parseFixed = (e: any) => ({ dt: parseFloat(e.nativeEvent.dt) });
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<VRTGameLoopView
|
|
68
|
+
onUpdate={onUpdate ? (e) => onUpdate(parse(e)) : undefined}
|
|
69
|
+
onLateUpdate={onLateUpdate ? (e) => onLateUpdate(parse(e)) : undefined}
|
|
70
|
+
onFixedUpdate={onFixedUpdate ? (e) => onFixedUpdate(parseFixed(e)) : undefined}
|
|
71
|
+
fixedHz={fixedHz}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ViroGameLoopUtils — direct node manipulation that bypasses the React reconciler.
|
|
3
|
+
*
|
|
4
|
+
* Call these from inside useGameLoop / onUpdate callbacks for zero-setState
|
|
5
|
+
* positional updates. The native NodeModule methods dispatch directly to the
|
|
6
|
+
* render thread via the existing VRONode::setPositionAtomic() infrastructure.
|
|
7
|
+
*
|
|
8
|
+
* Copyright © 2026 ReactVision. All rights reserved.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { findNodeHandle, UIManager } from "react-native";
|
|
12
|
+
import type React from "react";
|
|
13
|
+
|
|
14
|
+
export const ViroGameLoopUtils = {
|
|
15
|
+
/**
|
|
16
|
+
* Set the node's position without going through React state.
|
|
17
|
+
* Equivalent to <ViroNode position={[x,y,z]} /> but synchronous
|
|
18
|
+
* and reconciler-free — safe to call every frame.
|
|
19
|
+
*/
|
|
20
|
+
setPosition(
|
|
21
|
+
nodeRef: React.RefObject<any>,
|
|
22
|
+
position: [number, number, number]
|
|
23
|
+
): void {
|
|
24
|
+
const handle = findNodeHandle(nodeRef.current);
|
|
25
|
+
if (handle == null) return;
|
|
26
|
+
UIManager.dispatchViewManagerCommand(handle, "setPosition", position);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Set Euler rotation (degrees) without going through React state.
|
|
31
|
+
*/
|
|
32
|
+
setRotation(
|
|
33
|
+
nodeRef: React.RefObject<any>,
|
|
34
|
+
rotation: [number, number, number]
|
|
35
|
+
): void {
|
|
36
|
+
const handle = findNodeHandle(nodeRef.current);
|
|
37
|
+
if (handle == null) return;
|
|
38
|
+
UIManager.dispatchViewManagerCommand(handle, "setRotationEuler", rotation);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Set uniform or non-uniform scale without going through React state.
|
|
43
|
+
*/
|
|
44
|
+
setScale(
|
|
45
|
+
nodeRef: React.RefObject<any>,
|
|
46
|
+
scale: [number, number, number]
|
|
47
|
+
): void {
|
|
48
|
+
const handle = findNodeHandle(nodeRef.current);
|
|
49
|
+
if (handle == null) return;
|
|
50
|
+
UIManager.dispatchViewManagerCommand(handle, "setScale", scale);
|
|
51
|
+
},
|
|
52
|
+
};
|