@reactvision/react-viro 2.54.0 → 2.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +85 -46
- package/android/react_viro/react_viro-release.aar +0 -0
- package/android/viro_renderer/viro_renderer-release.aar +0 -0
- package/components/AR/ViroARCamera.tsx +5 -0
- package/components/AR/ViroARImageMarker.tsx +5 -0
- package/components/AR/ViroARObjectMarker.tsx +5 -0
- package/components/AR/ViroARPlane.tsx +5 -0
- package/components/AR/ViroARPlaneSelector.tsx +5 -0
- package/components/AR/ViroARScene.tsx +5 -0
- package/components/AR/ViroARSceneNavigator.tsx +54 -0
- package/components/Studio/StudioARScene.tsx +368 -0
- package/components/Studio/StudioSceneNavigator.tsx +191 -0
- package/components/Studio/VRTStudioModule.ts +40 -0
- package/components/Studio/domain/animationRegistry.ts +86 -0
- package/components/Studio/domain/collisionBindingsRuntime.ts +93 -0
- package/components/Studio/domain/collisionPairKey.ts +15 -0
- package/components/Studio/domain/dragConfiguration.ts +48 -0
- package/components/Studio/domain/materialConfig.ts +276 -0
- package/components/Studio/domain/physicsConfig.ts +204 -0
- package/components/Studio/domain/sceneNavigationHandler.ts +150 -0
- package/components/Studio/domain/studioMaterials.ts +33 -0
- package/components/Studio/domain/triggerImageRegistry.ts +64 -0
- package/components/Studio/domain/useStudioShaderTimeUniforms.ts +51 -0
- package/components/Studio/domain/useStudioShaderViewportUniforms.ts +52 -0
- package/components/Studio/domain/viroNodeFactory.tsx +323 -0
- package/components/Studio/index.ts +18 -0
- package/components/Studio/types.ts +164 -0
- package/components/Types/ViroEvents.ts +53 -0
- package/components/Utilities/VRModuleOpenXR.ts +50 -0
- package/components/Utilities/VRQuestNavigatorBridge.ts +168 -0
- package/components/Utilities/ViroPlatform.ts +52 -0
- package/components/Utilities/ViroVersion.ts +1 -1
- package/components/Utilities/useAnySourceHover.ts +55 -0
- package/components/Utilities/useAnySourcePressed.ts +70 -0
- package/components/ViroQuestEntryPoint.tsx +79 -0
- package/components/ViroVRSceneNavigator.tsx +44 -19
- package/components/ViroXRSceneNavigator.tsx +217 -0
- package/components/VisionOS/ViroVisionOSModule.ts +93 -0
- package/dist/components/AR/ViroARCamera.d.ts +1 -1
- package/dist/components/AR/ViroARCamera.js +5 -0
- package/dist/components/AR/ViroARImageMarker.d.ts +1 -1
- package/dist/components/AR/ViroARImageMarker.js +5 -0
- package/dist/components/AR/ViroARObjectMarker.d.ts +1 -1
- package/dist/components/AR/ViroARObjectMarker.js +5 -0
- package/dist/components/AR/ViroARPlane.d.ts +1 -1
- package/dist/components/AR/ViroARPlane.js +5 -0
- package/dist/components/AR/ViroARPlaneSelector.d.ts +1 -1
- package/dist/components/AR/ViroARPlaneSelector.js +5 -0
- package/dist/components/AR/ViroARScene.d.ts +1 -1
- package/dist/components/AR/ViroARScene.js +5 -0
- package/dist/components/AR/ViroARSceneNavigator.d.ts +13 -0
- package/dist/components/AR/ViroARSceneNavigator.js +36 -0
- package/dist/components/Studio/StudioARScene.d.ts +15 -0
- package/dist/components/Studio/StudioARScene.js +299 -0
- package/dist/components/Studio/StudioSceneNavigator.d.ts +31 -0
- package/dist/components/Studio/StudioSceneNavigator.js +174 -0
- package/dist/components/Studio/VRTStudioModule.d.ts +15 -0
- package/dist/components/Studio/VRTStudioModule.js +31 -0
- package/dist/components/Studio/domain/animationRegistry.d.ts +11 -0
- package/dist/components/Studio/domain/animationRegistry.js +67 -0
- package/dist/components/Studio/domain/collisionBindingsRuntime.d.ts +21 -0
- package/dist/components/Studio/domain/collisionBindingsRuntime.js +54 -0
- package/dist/components/Studio/domain/collisionPairKey.d.ts +8 -0
- package/dist/components/Studio/domain/collisionPairKey.js +15 -0
- package/dist/components/Studio/domain/dragConfiguration.d.ts +20 -0
- package/dist/components/Studio/domain/dragConfiguration.js +37 -0
- package/dist/components/Studio/domain/materialConfig.d.ts +56 -0
- package/dist/components/Studio/domain/materialConfig.js +239 -0
- package/dist/components/Studio/domain/physicsConfig.d.ts +69 -0
- package/dist/components/Studio/domain/physicsConfig.js +165 -0
- package/dist/components/Studio/domain/sceneNavigationHandler.d.ts +12 -0
- package/dist/components/Studio/domain/sceneNavigationHandler.js +112 -0
- package/dist/components/Studio/domain/studioMaterials.d.ts +6 -0
- package/dist/components/Studio/domain/studioMaterials.js +30 -0
- package/dist/components/Studio/domain/triggerImageRegistry.d.ts +13 -0
- package/dist/components/Studio/domain/triggerImageRegistry.js +47 -0
- package/dist/components/Studio/domain/useStudioShaderTimeUniforms.d.ts +6 -0
- package/dist/components/Studio/domain/useStudioShaderTimeUniforms.js +48 -0
- package/dist/components/Studio/domain/useStudioShaderViewportUniforms.d.ts +6 -0
- package/dist/components/Studio/domain/useStudioShaderViewportUniforms.js +48 -0
- package/dist/components/Studio/domain/viroNodeFactory.d.ts +28 -0
- package/dist/components/Studio/domain/viroNodeFactory.js +193 -0
- package/dist/components/Studio/index.d.ts +3 -0
- package/dist/components/Studio/index.js +7 -0
- package/dist/components/Studio/types.d.ts +149 -0
- package/dist/components/Studio/types.js +4 -0
- package/dist/components/Types/ViroEvents.d.ts +49 -1
- package/dist/components/Types/ViroEvents.js +1 -0
- package/dist/components/Utilities/VRModuleOpenXR.d.ts +32 -0
- package/dist/components/Utilities/VRModuleOpenXR.js +44 -0
- package/dist/components/Utilities/VRQuestNavigatorBridge.d.ts +85 -0
- package/dist/components/Utilities/VRQuestNavigatorBridge.js +124 -0
- package/dist/components/Utilities/ViroPlatform.d.ts +10 -0
- package/dist/components/Utilities/ViroPlatform.js +43 -0
- package/dist/components/Utilities/ViroVersion.d.ts +1 -1
- package/dist/components/Utilities/ViroVersion.js +1 -1
- package/dist/components/Utilities/useAnySourceHover.d.ts +36 -0
- package/dist/components/Utilities/useAnySourceHover.js +48 -0
- package/dist/components/Utilities/useAnySourcePressed.d.ts +37 -0
- package/dist/components/Utilities/useAnySourcePressed.js +61 -0
- package/dist/components/ViroQuestEntryPoint.d.ts +13 -0
- package/dist/components/ViroQuestEntryPoint.js +104 -0
- package/dist/components/ViroVRSceneNavigator.d.ts +24 -10
- package/dist/components/ViroVRSceneNavigator.js +21 -18
- package/dist/components/ViroXRSceneNavigator.d.ts +54 -0
- package/dist/components/ViroXRSceneNavigator.js +173 -0
- package/dist/components/VisionOS/ViroVisionOSModule.d.ts +65 -0
- package/dist/components/VisionOS/ViroVisionOSModule.js +91 -0
- package/dist/index.d.ts +15 -2
- package/dist/index.js +32 -2
- package/dist/plugins/withViro.d.ts +17 -1
- package/dist/plugins/withViroAndroid.js +312 -7
- package/dist/plugins/withViroIos.js +5 -0
- package/dist/plugins/withViroVisionOS.d.ts +24 -0
- package/dist/plugins/withViroVisionOS.js +265 -0
- package/index.ts +58 -0
- package/ios/ViroReact.podspec +13 -4
- 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/VROARSession.h +10 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROARSessioniOS.h +4 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROInputControllerBase.h +74 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROInputType.h +11 -3
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VROPlatformUtil.h +13 -0
- package/ios/dist/ViroRenderer/ViroKit.framework/Headers/VRORenderer.h +8 -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.podspec +5 -0
- package/ios/dist/include/VRTARSceneNavigator.h +3 -0
- package/ios/dist/include/VRTStudioModule.h +6 -0
- package/ios/dist/lib/libViroReact.a +0 -0
- package/package.json +1 -1
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Alert } from "react-native";
|
|
2
|
+
import { isQuest } from "../../Utilities/ViroPlatform";
|
|
3
|
+
import { StudioAnimation, StudioSceneFunction, StudioSceneResponse } from "../types";
|
|
4
|
+
import { VRTStudioModule } from "../VRTStudioModule";
|
|
5
|
+
|
|
6
|
+
type SceneNavigator = any; // ViroARSceneNavigator navigator object passed to AR scenes
|
|
7
|
+
|
|
8
|
+
const ANIMATION_CHAIN_MAX_DEPTH = 10;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Resolves a scene function by ID from a flat list.
|
|
12
|
+
*/
|
|
13
|
+
function resolveById(
|
|
14
|
+
id: string,
|
|
15
|
+
fns: StudioSceneFunction[]
|
|
16
|
+
): StudioSceneFunction | undefined {
|
|
17
|
+
return fns.find((f) => f.id === id);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Looks up target_asset_id for an ANIMATION-type scene function.
|
|
22
|
+
* The inline scene_animation only has the animation UUID — we resolve it
|
|
23
|
+
* from the top-level animations array.
|
|
24
|
+
*/
|
|
25
|
+
function resolveAnimationTargetAssetId(
|
|
26
|
+
animationId: string,
|
|
27
|
+
animations: StudioAnimation[]
|
|
28
|
+
): string | undefined {
|
|
29
|
+
return animations.find((a) => a.id === animationId)?.target_asset_id;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Single dispatcher for all scene function types.
|
|
34
|
+
* Used by onClick, onCollision, and on_load_function triggers.
|
|
35
|
+
*/
|
|
36
|
+
export function executeFunctionWithRelations(
|
|
37
|
+
fn: StudioSceneFunction,
|
|
38
|
+
sceneNavigator: SceneNavigator | undefined,
|
|
39
|
+
animations: StudioAnimation[],
|
|
40
|
+
onAnimationTrigger?: (targetAssetId: string, animationKey: string) => void,
|
|
41
|
+
depth = 0,
|
|
42
|
+
onSceneChange?: (sceneId: string, sceneName: string) => void,
|
|
43
|
+
): void {
|
|
44
|
+
if (depth > ANIMATION_CHAIN_MAX_DEPTH) {
|
|
45
|
+
console.warn(
|
|
46
|
+
`[Studio] Max animation chain depth (${ANIMATION_CHAIN_MAX_DEPTH}) exceeded for function ${fn.id}.`
|
|
47
|
+
);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (fn.function_type === "NAVIGATION") {
|
|
52
|
+
const nav = fn.scene_navigation;
|
|
53
|
+
if (!nav?.navigate_to || !sceneNavigator) return;
|
|
54
|
+
void navigateToScene(sceneNavigator, nav.navigate_to, animations, onSceneChange);
|
|
55
|
+
} else if (fn.function_type === "ALERT") {
|
|
56
|
+
const alrt = fn.scene_alert;
|
|
57
|
+
if (!alrt) return;
|
|
58
|
+
if (isQuest) {
|
|
59
|
+
// Alert.alert shows a 2D panel dialog — invisible in the VR compositor.
|
|
60
|
+
// Log it so it's not silently swallowed; in-scene VR alert UI is a TODO.
|
|
61
|
+
console.warn(
|
|
62
|
+
`[Studio] Alert (Quest — not shown in VR): "${alrt.alert_title}" — ${alrt.alert_message}`
|
|
63
|
+
);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
Alert.alert(alrt.alert_title ?? "Alert", alrt.alert_message ?? "", [
|
|
67
|
+
{ text: "OK", style: "default" },
|
|
68
|
+
]);
|
|
69
|
+
} else if (fn.function_type === "ANIMATION") {
|
|
70
|
+
const anim = fn.scene_animation;
|
|
71
|
+
if (!anim || !onAnimationTrigger) return;
|
|
72
|
+
|
|
73
|
+
const animLookupId = fn.animation ?? anim.id;
|
|
74
|
+
const targetAssetId = resolveAnimationTargetAssetId(animLookupId, animations);
|
|
75
|
+
if (!targetAssetId) {
|
|
76
|
+
console.warn(
|
|
77
|
+
`[Studio] ANIMATION function ${fn.id}: could not resolve target_asset_id for animation ${anim.id}`
|
|
78
|
+
);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
onAnimationTrigger(targetAssetId, anim.animation_key);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Executes the scene's on_load_function if set.
|
|
87
|
+
*/
|
|
88
|
+
export function executeOnLoadFunction(
|
|
89
|
+
functionId: string,
|
|
90
|
+
functions: StudioSceneFunction[],
|
|
91
|
+
sceneNavigator: SceneNavigator | undefined,
|
|
92
|
+
animations: StudioAnimation[],
|
|
93
|
+
onAnimationTrigger?: (targetAssetId: string, animationKey: string) => void,
|
|
94
|
+
onSceneChange?: (sceneId: string, sceneName: string) => void,
|
|
95
|
+
): void {
|
|
96
|
+
const fn = resolveById(functionId, functions);
|
|
97
|
+
if (!fn) {
|
|
98
|
+
console.warn(`[Studio] on_load_function ${functionId} not found.`);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
executeFunctionWithRelations(fn, sceneNavigator, animations, onAnimationTrigger, 0, onSceneChange);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Navigates to a new AR scene by fetching its data via rvGetScene and
|
|
106
|
+
* pushing it onto the ViroARSceneNavigator stack.
|
|
107
|
+
*
|
|
108
|
+
* The sceneNavigator object exposes rvGetScene as a method — no separate
|
|
109
|
+
* API client needed here.
|
|
110
|
+
*/
|
|
111
|
+
async function navigateToScene(
|
|
112
|
+
sceneNavigator: SceneNavigator,
|
|
113
|
+
targetSceneId: string,
|
|
114
|
+
currentAnimations: StudioAnimation[],
|
|
115
|
+
onSceneChange?: (sceneId: string, sceneName: string) => void,
|
|
116
|
+
): Promise<void> {
|
|
117
|
+
if (!sceneNavigator) {
|
|
118
|
+
console.error("[Studio] SceneNavigator not available for navigation");
|
|
119
|
+
Alert.alert("Navigation Error", "Unable to navigate to scene");
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
console.log(`[Studio] Navigating to scene: ${targetSceneId}`);
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const result = await VRTStudioModule.rvGetScene(targetSceneId);
|
|
127
|
+
if (!result?.success) {
|
|
128
|
+
throw new Error(result?.error ?? "rvGetScene failed");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const sceneData: StudioSceneResponse = JSON.parse(result.data!);
|
|
132
|
+
|
|
133
|
+
// Lazy import to avoid circular dependency
|
|
134
|
+
const { StudioARScene } = require("../StudioARScene");
|
|
135
|
+
|
|
136
|
+
sceneNavigator.push({
|
|
137
|
+
scene: StudioARScene,
|
|
138
|
+
passProps: {
|
|
139
|
+
sceneData,
|
|
140
|
+
onSceneChange,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
onSceneChange?.(targetSceneId, sceneData.scene.name ?? targetSceneId);
|
|
145
|
+
console.log(`[Studio] Navigated to scene: ${sceneData.scene.name}`);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error("[Studio] Error navigating to scene:", error);
|
|
148
|
+
Alert.alert("Navigation Error", "Failed to load scene");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ViroMaterials } from "../../Material/ViroMaterials";
|
|
2
|
+
import { StudioAsset } from "../types";
|
|
3
|
+
import {
|
|
4
|
+
buildViroMaterialDefinition,
|
|
5
|
+
parseMaterialConfig,
|
|
6
|
+
studioMaterialName,
|
|
7
|
+
} from "./materialConfig";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Registers Viro materials for scene assets that have a valid `material_config`.
|
|
11
|
+
* Call synchronously after fetching assets and before rendering AR nodes.
|
|
12
|
+
*/
|
|
13
|
+
export function registerStudioMaterialsForAssets(assets: StudioAsset[]): void {
|
|
14
|
+
const materials: Record<string, ReturnType<typeof buildViroMaterialDefinition>> = {};
|
|
15
|
+
|
|
16
|
+
for (const asset of assets) {
|
|
17
|
+
if (asset.asset_type_name !== "3D-MODEL") continue;
|
|
18
|
+
if (asset.material_config == null) continue;
|
|
19
|
+
|
|
20
|
+
const config = parseMaterialConfig(asset.material_config);
|
|
21
|
+
if (!config) continue;
|
|
22
|
+
|
|
23
|
+
materials[studioMaterialName(asset.id)] = buildViroMaterialDefinition(config);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (Object.keys(materials).length === 0) return;
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
ViroMaterials.createMaterials(materials as any);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error("[studioMaterials] ViroMaterials.createMaterials failed", err);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { ViroARTrackingTargets } from "../../AR/ViroARTrackingTargets";
|
|
2
|
+
import { StudioAsset } from "../types";
|
|
3
|
+
|
|
4
|
+
const DEFAULT_PHYSICAL_WIDTH = 0.2; // meters
|
|
5
|
+
const DEFAULT_ORIENTATION = "Up" as const;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Registers trigger image targets with ViroReact for image recognition.
|
|
9
|
+
* One target per asset with trigger_image_url.
|
|
10
|
+
* Must be called before rendering ViroARImageMarker components.
|
|
11
|
+
*
|
|
12
|
+
* @returns Map from trigger_image_url → target name for lookup in ViroARImageMarker
|
|
13
|
+
*/
|
|
14
|
+
export function registerTriggerImageTargets(
|
|
15
|
+
assets: StudioAsset[]
|
|
16
|
+
): Map<string, string> {
|
|
17
|
+
const assetsWithTrigger = assets.filter(
|
|
18
|
+
(a): a is StudioAsset & { trigger_image_url: string } =>
|
|
19
|
+
!!a.trigger_image_url
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (assetsWithTrigger.length === 0) {
|
|
23
|
+
return new Map();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const urlToTargetName = new Map<string, string>();
|
|
27
|
+
const targets: Record<
|
|
28
|
+
string,
|
|
29
|
+
{
|
|
30
|
+
source: { uri: string };
|
|
31
|
+
orientation: string;
|
|
32
|
+
physicalWidth: number;
|
|
33
|
+
type: string;
|
|
34
|
+
}
|
|
35
|
+
> = {};
|
|
36
|
+
|
|
37
|
+
assetsWithTrigger.forEach((asset, index) => {
|
|
38
|
+
const targetName = `studio-trigger-${index}`;
|
|
39
|
+
urlToTargetName.set(asset.trigger_image_url, targetName);
|
|
40
|
+
targets[targetName] = {
|
|
41
|
+
source: { uri: asset.trigger_image_url },
|
|
42
|
+
orientation: asset.trigger_image_orientation ?? DEFAULT_ORIENTATION,
|
|
43
|
+
physicalWidth:
|
|
44
|
+
asset.trigger_image_physical_width_m ?? DEFAULT_PHYSICAL_WIDTH,
|
|
45
|
+
type: "Image",
|
|
46
|
+
};
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
ViroARTrackingTargets.createTargets(targets);
|
|
50
|
+
return urlToTargetName;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Cleans up trigger image targets when the scene unmounts.
|
|
55
|
+
*/
|
|
56
|
+
export function cleanupTriggerImageTargets(targetNames: string[]): void {
|
|
57
|
+
targetNames.forEach((name) => {
|
|
58
|
+
try {
|
|
59
|
+
ViroARTrackingTargets.deleteTarget(name);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.warn(`[Studio] Failed to delete trigger target "${name}":`, error);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { ViroMaterials } from "../../Material/ViroMaterials";
|
|
3
|
+
import { StudioAsset } from "../types";
|
|
4
|
+
import {
|
|
5
|
+
materialConfigNeedsTimeUniform,
|
|
6
|
+
parseMaterialConfig,
|
|
7
|
+
studioMaterialName,
|
|
8
|
+
} from "./materialConfig";
|
|
9
|
+
|
|
10
|
+
/** ~60fps — matches Viro starter kit / shader docs. */
|
|
11
|
+
const TIME_TICK_MS = 16;
|
|
12
|
+
|
|
13
|
+
function computeTimeUniformPayload(assets: StudioAsset[]): { key: string; names: string[] } {
|
|
14
|
+
const names: string[] = [];
|
|
15
|
+
for (const asset of assets) {
|
|
16
|
+
if (asset.asset_type_name !== "3D-MODEL") continue;
|
|
17
|
+
if (asset.material_config == null) continue;
|
|
18
|
+
const config = parseMaterialConfig(asset.material_config);
|
|
19
|
+
if (!config || !materialConfigNeedsTimeUniform(config)) continue;
|
|
20
|
+
names.push(studioMaterialName(asset.id));
|
|
21
|
+
}
|
|
22
|
+
names.sort();
|
|
23
|
+
return { key: names.join("|"), names };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Drives the `time` shader uniform for materials that use animated presets.
|
|
28
|
+
* Uses `setInterval(16)` and `Date.now() % 1000000` like working Viro starter kits.
|
|
29
|
+
*/
|
|
30
|
+
export function useStudioShaderTimeUniforms(assets: StudioAsset[]): void {
|
|
31
|
+
const payload = useMemo(() => computeTimeUniformPayload(assets), [assets]);
|
|
32
|
+
|
|
33
|
+
const payloadRef = useRef(payload);
|
|
34
|
+
payloadRef.current = payload;
|
|
35
|
+
|
|
36
|
+
const applyTimeUniforms = useCallback(() => {
|
|
37
|
+
const { names } = payloadRef.current;
|
|
38
|
+
if (names.length === 0) return;
|
|
39
|
+
const time = Date.now() % 1_000_000;
|
|
40
|
+
for (const name of names) {
|
|
41
|
+
ViroMaterials.updateShaderUniform(name, "time", "float", time);
|
|
42
|
+
}
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (payload.names.length === 0) return;
|
|
47
|
+
const id = setInterval(applyTimeUniforms, TIME_TICK_MS);
|
|
48
|
+
applyTimeUniforms();
|
|
49
|
+
return () => clearInterval(id);
|
|
50
|
+
}, [payload.key, applyTimeUniforms]);
|
|
51
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { useCallback, useEffect, useMemo } from "react";
|
|
2
|
+
import { Dimensions, PixelRatio } from "react-native";
|
|
3
|
+
import { ViroMaterials } from "../../Material/ViroMaterials";
|
|
4
|
+
import { StudioAsset } from "../types";
|
|
5
|
+
import {
|
|
6
|
+
materialConfigNeedsViewportUniforms,
|
|
7
|
+
parseMaterialConfig,
|
|
8
|
+
studioMaterialName,
|
|
9
|
+
} from "./materialConfig";
|
|
10
|
+
|
|
11
|
+
function computeViewportPayload(assets: StudioAsset[]): {
|
|
12
|
+
key: string;
|
|
13
|
+
names: string[];
|
|
14
|
+
} {
|
|
15
|
+
const names: string[] = [];
|
|
16
|
+
for (const asset of assets) {
|
|
17
|
+
if (asset.asset_type_name !== "3D-MODEL") continue;
|
|
18
|
+
if (asset.material_config == null) continue;
|
|
19
|
+
const config = parseMaterialConfig(asset.material_config);
|
|
20
|
+
if (!config || !materialConfigNeedsViewportUniforms(config)) continue;
|
|
21
|
+
names.push(studioMaterialName(asset.id));
|
|
22
|
+
}
|
|
23
|
+
names.sort();
|
|
24
|
+
return { key: names.join("|"), names };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Pushes _rf_vpw / _rf_vph (physical pixel dimensions) to materials that sample the camera
|
|
29
|
+
* feed via gl_FragCoord. Called on mount and whenever the screen dimensions change.
|
|
30
|
+
*/
|
|
31
|
+
export function useStudioShaderViewportUniforms(assets: StudioAsset[]): void {
|
|
32
|
+
const payload = useMemo(() => computeViewportPayload(assets), [assets]);
|
|
33
|
+
|
|
34
|
+
const pushViewport = useCallback(() => {
|
|
35
|
+
if (payload.names.length === 0) return;
|
|
36
|
+
const { width, height } = Dimensions.get("screen");
|
|
37
|
+
const pr = PixelRatio.get();
|
|
38
|
+
const pw = width * pr;
|
|
39
|
+
const ph = height * pr;
|
|
40
|
+
for (const name of payload.names) {
|
|
41
|
+
ViroMaterials.updateShaderUniform(name, "_rf_vpw", "float", pw);
|
|
42
|
+
ViroMaterials.updateShaderUniform(name, "_rf_vph", "float", ph);
|
|
43
|
+
}
|
|
44
|
+
}, [payload]);
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (payload.names.length === 0) return;
|
|
48
|
+
pushViewport();
|
|
49
|
+
const sub = Dimensions.addEventListener("change", pushViewport);
|
|
50
|
+
return () => sub.remove();
|
|
51
|
+
}, [payload.key, pushViewport]);
|
|
52
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Platform } from "react-native";
|
|
3
|
+
import { Viro3DObject } from "../../Viro3DObject";
|
|
4
|
+
import { ViroImage } from "../../ViroImage";
|
|
5
|
+
import { ViroText } from "../../ViroText";
|
|
6
|
+
import { ViroVideo } from "../../ViroVideo";
|
|
7
|
+
import { StudioAnimation, StudioAsset, StudioSceneMeta, ViroAnimationProp } from "../types";
|
|
8
|
+
import { executeFunctionWithRelations } from "./sceneNavigationHandler";
|
|
9
|
+
import { parseMaterialConfig, studioMaterialName } from "./materialConfig";
|
|
10
|
+
import { DragConfiguration } from "./dragConfiguration";
|
|
11
|
+
import { buildViroPhysicsBody, parsePhysicsBodyConfig } from "./physicsConfig";
|
|
12
|
+
|
|
13
|
+
type SceneNavigator = any;
|
|
14
|
+
|
|
15
|
+
export type NodeConfig = {
|
|
16
|
+
position: [number, number, number];
|
|
17
|
+
rotation: [number, number, number];
|
|
18
|
+
scale: [number, number, number];
|
|
19
|
+
dragType?: "FixedDistance" | "FixedDistanceOrigin" | "FixedToWorld" | "FixedToPlane";
|
|
20
|
+
dragPlane?: { planePoint: [number, number, number]; planeNormal: [number, number, number]; maxDistance: number };
|
|
21
|
+
physicsBody?: Record<string, unknown>;
|
|
22
|
+
viroTag?: string;
|
|
23
|
+
onClick?: () => void;
|
|
24
|
+
animation?: ViroAnimationProp;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Derives the transform config for an asset.
|
|
29
|
+
* Clamps Z to -2 for non-trigger assets to guarantee visibility.
|
|
30
|
+
*/
|
|
31
|
+
export function createNodeConfig(
|
|
32
|
+
asset: StudioAsset,
|
|
33
|
+
sceneNavigator: SceneNavigator | undefined,
|
|
34
|
+
animations: StudioAnimation[],
|
|
35
|
+
scene: StudioSceneMeta | null,
|
|
36
|
+
onAnimationTrigger?: (targetAssetId: string, animKey: string) => void,
|
|
37
|
+
animationStates?: Record<string, ViroAnimationProp>,
|
|
38
|
+
onSceneChange?: (sceneId: string, sceneName: string) => void
|
|
39
|
+
): NodeConfig {
|
|
40
|
+
const hasTriggerImage = !!asset.trigger_image_url;
|
|
41
|
+
|
|
42
|
+
let posZ = asset.position_z ?? -2;
|
|
43
|
+
if (!hasTriggerImage && posZ > -0.5) {
|
|
44
|
+
console.warn(
|
|
45
|
+
`[Studio/NodeFactory] Asset "${asset.name}" Z=${posZ} too close, clamping to -2`
|
|
46
|
+
);
|
|
47
|
+
posZ = -2;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const position: [number, number, number] = [
|
|
51
|
+
asset.position_x ?? 0,
|
|
52
|
+
asset.position_y ?? 0,
|
|
53
|
+
posZ,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
// Apply trigger image orientation offset to rotation Z
|
|
57
|
+
let rotationZ = asset.rotation_z ?? 0;
|
|
58
|
+
if (hasTriggerImage && asset.trigger_image_orientation) {
|
|
59
|
+
const offsets: Record<string, number> = {
|
|
60
|
+
Left: -90,
|
|
61
|
+
Right: 90,
|
|
62
|
+
Down: 180,
|
|
63
|
+
Up: 0,
|
|
64
|
+
};
|
|
65
|
+
rotationZ += offsets[asset.trigger_image_orientation] ?? 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const rotation: [number, number, number] = [
|
|
69
|
+
asset.rotation_x ?? 0,
|
|
70
|
+
asset.rotation_y ?? 0,
|
|
71
|
+
rotationZ,
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
let scaleValue = asset.scale ?? 1;
|
|
75
|
+
if (scaleValue < 0.01) scaleValue = 0.1;
|
|
76
|
+
if (scaleValue > 10) scaleValue = 2;
|
|
77
|
+
const scale: [number, number, number] = [scaleValue, scaleValue, scaleValue];
|
|
78
|
+
|
|
79
|
+
const dragType = DragConfiguration.getDragType(asset, scene);
|
|
80
|
+
|
|
81
|
+
let dragPlane: NodeConfig["dragPlane"];
|
|
82
|
+
if (dragType === "FixedToPlane") {
|
|
83
|
+
dragPlane = DragConfiguration.getDragPlane(
|
|
84
|
+
scene?.plane_direction ?? "Horizontal",
|
|
85
|
+
position,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const parsedPhysics = parsePhysicsBodyConfig(asset.physics_config);
|
|
90
|
+
const physicsBody = parsedPhysics ? buildViroPhysicsBody(parsedPhysics) : undefined;
|
|
91
|
+
const viroTag = parsedPhysics ? asset.id : undefined;
|
|
92
|
+
|
|
93
|
+
const onClick = createOnClickHandler(
|
|
94
|
+
asset,
|
|
95
|
+
sceneNavigator,
|
|
96
|
+
animations,
|
|
97
|
+
onAnimationTrigger,
|
|
98
|
+
onSceneChange
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const animation = animationStates?.[asset.id];
|
|
102
|
+
|
|
103
|
+
return { position, rotation, scale, dragType, dragPlane, physicsBody, viroTag, onClick, animation };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createOnClickHandler(
|
|
107
|
+
asset: StudioAsset,
|
|
108
|
+
sceneNavigator: SceneNavigator | undefined,
|
|
109
|
+
animations: StudioAnimation[],
|
|
110
|
+
onAnimationTrigger?: (targetAssetId: string, animKey: string) => void,
|
|
111
|
+
onSceneChange?: (sceneId: string, sceneName: string) => void
|
|
112
|
+
): (() => void) | undefined {
|
|
113
|
+
const fn = asset.scene_function;
|
|
114
|
+
if (!fn) return undefined;
|
|
115
|
+
|
|
116
|
+
if (fn.function_type === "NAVIGATION" && !fn.scene_navigation?.navigate_to) {
|
|
117
|
+
console.warn(`[Studio] Asset "${asset.name}" has NAVIGATION but no target scene`);
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
if (fn.function_type === "ALERT" && !fn.scene_alert) {
|
|
121
|
+
console.warn(`[Studio] Asset "${asset.name}" has ALERT but no alert data`);
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
if (fn.function_type === "ANIMATION" && !fn.scene_animation) {
|
|
125
|
+
console.warn(`[Studio] Asset "${asset.name}" has ANIMATION but no animation data`);
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return () =>
|
|
130
|
+
executeFunctionWithRelations(fn, sceneNavigator, animations, onAnimationTrigger, 0, onSceneChange);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/** Resolves asset type from asset_type_name. */
|
|
134
|
+
function resolveType(
|
|
135
|
+
asset: StudioAsset
|
|
136
|
+
): "3D-MODEL" | "TEXT" | "IMAGE" | "VIDEO" | null {
|
|
137
|
+
return asset.asset_type_name ?? null;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Infers 3D model format from file extension. */
|
|
141
|
+
function inferModelType(url: string): "GLB" | "GLTF" | "OBJ" | "VRX" {
|
|
142
|
+
const ext = url.toLowerCase().split(".").pop();
|
|
143
|
+
if (ext === "gltf") return "GLTF";
|
|
144
|
+
if (ext === "obj") return "OBJ";
|
|
145
|
+
if (ext === "vrx") return "VRX";
|
|
146
|
+
return "GLB";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function create3DObject(
|
|
150
|
+
asset: StudioAsset,
|
|
151
|
+
config: NodeConfig,
|
|
152
|
+
onAssetLoaded?: (id: string) => void,
|
|
153
|
+
onCollision?: (viroTag: string, collidedPoint: [number, number, number], collidedNormal: [number, number, number]) => void
|
|
154
|
+
): React.ReactElement | null {
|
|
155
|
+
if (!asset.file_url) {
|
|
156
|
+
console.warn(`[Studio] 3D model "${asset.name}" has no file_url`);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const modelType = inferModelType(asset.file_url);
|
|
161
|
+
|
|
162
|
+
// Android: slightly reduce scale for stability
|
|
163
|
+
const scale =
|
|
164
|
+
Platform.OS === "android"
|
|
165
|
+
? ([
|
|
166
|
+
config.scale[0] * 0.8,
|
|
167
|
+
config.scale[1] * 0.8,
|
|
168
|
+
config.scale[2] * 0.8,
|
|
169
|
+
] as [number, number, number])
|
|
170
|
+
: config.scale;
|
|
171
|
+
|
|
172
|
+
const hasMaterialConfig = parseMaterialConfig(asset.material_config) !== null;
|
|
173
|
+
const shaderOverrides = hasMaterialConfig ? [studioMaterialName(asset.id)] : undefined;
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<Viro3DObject
|
|
177
|
+
key={asset.id}
|
|
178
|
+
source={{ uri: asset.file_url }}
|
|
179
|
+
position={config.position}
|
|
180
|
+
rotation={config.rotation}
|
|
181
|
+
scale={scale}
|
|
182
|
+
type={modelType}
|
|
183
|
+
dragType={config.dragType}
|
|
184
|
+
dragPlane={config.dragPlane}
|
|
185
|
+
animation={config.animation as any}
|
|
186
|
+
onClick={config.onClick}
|
|
187
|
+
renderingOrder={Platform.OS === "android" ? 1 : 0}
|
|
188
|
+
onLoadEnd={() => onAssetLoaded?.(asset.id)}
|
|
189
|
+
onError={(e) =>
|
|
190
|
+
console.error(`[Studio] 3D model "${asset.name}" error:`, e)
|
|
191
|
+
}
|
|
192
|
+
// Viro derives native canDrag from `onDrag != undefined`; without this prop
|
|
193
|
+
// the drag recognizer is never attached, even when dragType is set.
|
|
194
|
+
{...(config.dragType ? { onDrag: () => {} } : {})}
|
|
195
|
+
{...(shaderOverrides ? { shaderOverrides } : {})}
|
|
196
|
+
{...(config.physicsBody ? { physicsBody: config.physicsBody as any, viroTag: config.viroTag } : {})}
|
|
197
|
+
{...(onCollision ? { onCollision: onCollision as any } : {})}
|
|
198
|
+
/>
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function createImage(
|
|
203
|
+
asset: StudioAsset,
|
|
204
|
+
config: NodeConfig,
|
|
205
|
+
onAssetLoaded?: (id: string) => void
|
|
206
|
+
): React.ReactElement | null {
|
|
207
|
+
if (!asset.file_url) {
|
|
208
|
+
console.warn(`[Studio] Image "${asset.name}" has no file_url`);
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<ViroImage
|
|
214
|
+
key={asset.id}
|
|
215
|
+
source={{ uri: asset.file_url }}
|
|
216
|
+
position={config.position}
|
|
217
|
+
rotation={config.rotation}
|
|
218
|
+
scale={config.scale}
|
|
219
|
+
dragType={config.dragType}
|
|
220
|
+
animation={config.animation as any}
|
|
221
|
+
onClick={config.onClick}
|
|
222
|
+
onLoadEnd={() => onAssetLoaded?.(asset.id)}
|
|
223
|
+
onError={(e) =>
|
|
224
|
+
console.error(`[Studio] Image "${asset.name}" error:`, e)
|
|
225
|
+
}
|
|
226
|
+
{...(config.dragType ? { onDrag: () => {} } : {})}
|
|
227
|
+
/>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function createText(
|
|
232
|
+
asset: StudioAsset,
|
|
233
|
+
config: NodeConfig
|
|
234
|
+
): React.ReactElement {
|
|
235
|
+
return (
|
|
236
|
+
<ViroText
|
|
237
|
+
key={asset.id}
|
|
238
|
+
text={asset.name ?? ""}
|
|
239
|
+
position={config.position}
|
|
240
|
+
rotation={config.rotation}
|
|
241
|
+
scale={config.scale}
|
|
242
|
+
dragType={config.dragType}
|
|
243
|
+
animation={config.animation as any}
|
|
244
|
+
onClick={config.onClick}
|
|
245
|
+
style={{
|
|
246
|
+
fontFamily: "Arial",
|
|
247
|
+
fontSize: 20,
|
|
248
|
+
color: "#FFFFFF",
|
|
249
|
+
textAlign: "center",
|
|
250
|
+
}}
|
|
251
|
+
{...(config.dragType ? { onDrag: () => {} } : {})}
|
|
252
|
+
/>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function createVideo(
|
|
257
|
+
asset: StudioAsset,
|
|
258
|
+
config: NodeConfig
|
|
259
|
+
): React.ReactElement | null {
|
|
260
|
+
if (!asset.file_url) {
|
|
261
|
+
console.warn(`[Studio] Video "${asset.name}" has no file_url`);
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<ViroVideo
|
|
267
|
+
key={asset.id}
|
|
268
|
+
source={{ uri: asset.file_url }}
|
|
269
|
+
position={config.position}
|
|
270
|
+
rotation={config.rotation}
|
|
271
|
+
scale={config.scale}
|
|
272
|
+
dragType={config.dragType}
|
|
273
|
+
animation={config.animation as any}
|
|
274
|
+
onClick={config.onClick}
|
|
275
|
+
loop={true}
|
|
276
|
+
muted={false}
|
|
277
|
+
onError={(e) =>
|
|
278
|
+
console.error(`[Studio] Video "${asset.name}" error:`, e)
|
|
279
|
+
}
|
|
280
|
+
{...(config.dragType ? { onDrag: () => {} } : {})}
|
|
281
|
+
/>
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Creates the appropriate Viro component for a StudioAsset.
|
|
287
|
+
*/
|
|
288
|
+
export function createNode(
|
|
289
|
+
asset: StudioAsset,
|
|
290
|
+
sceneNavigator: SceneNavigator | undefined,
|
|
291
|
+
animations: StudioAnimation[],
|
|
292
|
+
scene: StudioSceneMeta | null,
|
|
293
|
+
onAnimationTrigger?: (targetAssetId: string, animKey: string) => void,
|
|
294
|
+
animationStates?: Record<string, ViroAnimationProp>,
|
|
295
|
+
onAssetLoaded?: (id: string) => void,
|
|
296
|
+
onCollision?: (viroTag: string, collidedPoint: [number, number, number], collidedNormal: [number, number, number]) => void,
|
|
297
|
+
onSceneChange?: (sceneId: string, sceneName: string) => void
|
|
298
|
+
): React.ReactElement | null {
|
|
299
|
+
const type = resolveType(asset);
|
|
300
|
+
const config = createNodeConfig(
|
|
301
|
+
asset,
|
|
302
|
+
sceneNavigator,
|
|
303
|
+
animations,
|
|
304
|
+
scene,
|
|
305
|
+
onAnimationTrigger,
|
|
306
|
+
animationStates,
|
|
307
|
+
onSceneChange
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
switch (type) {
|
|
311
|
+
case "3D-MODEL":
|
|
312
|
+
return create3DObject(asset, config, onAssetLoaded, onCollision);
|
|
313
|
+
case "IMAGE":
|
|
314
|
+
return createImage(asset, config, onAssetLoaded);
|
|
315
|
+
case "TEXT":
|
|
316
|
+
return createText(asset, config);
|
|
317
|
+
case "VIDEO":
|
|
318
|
+
return createVideo(asset, config);
|
|
319
|
+
default:
|
|
320
|
+
console.warn(`[Studio] Unknown asset type "${type}" for "${asset.name}"`);
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { StudioARScene } from "./StudioARScene";
|
|
2
|
+
export { StudioSceneNavigator } from "./StudioSceneNavigator";
|
|
3
|
+
export type {
|
|
4
|
+
StudioAnimation,
|
|
5
|
+
StudioAsset,
|
|
6
|
+
StudioCollisionBinding,
|
|
7
|
+
StudioProjectApiResponse,
|
|
8
|
+
StudioProjectAsset,
|
|
9
|
+
StudioProjectMeta,
|
|
10
|
+
StudioProjectOpeningScene,
|
|
11
|
+
StudioProjectOverview,
|
|
12
|
+
StudioProjectSceneSummary,
|
|
13
|
+
StudioSceneCreatedBy,
|
|
14
|
+
StudioSceneFunction,
|
|
15
|
+
StudioSceneMeta,
|
|
16
|
+
StudioSceneResponse,
|
|
17
|
+
ViroAnimationProp,
|
|
18
|
+
} from "./types";
|