@liveroom-tech/react-immersive 5.1.0 → 5.2.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 +486 -57
- package/dist/binding-builder.css +1 -1
- package/dist/binding-builder.js +17 -13
- package/dist/binding-builder.mjs +1 -1
- package/dist/chunk-4NLJSD3S.mjs +1 -0
- package/dist/chunk-B7REIGDG.mjs +1 -0
- package/dist/{chunk-WN6GMMHF.mjs → chunk-HYFJ5DYJ.mjs} +1 -1
- package/dist/chunk-QC3NT76G.mjs +3 -0
- package/dist/chunk-T3M3BQ7H.mjs +32 -0
- package/dist/chunk-ZPK67UG7.mjs +1 -0
- package/dist/hosted.css +1 -1
- package/dist/hosted.d.mts +4 -3
- package/dist/hosted.d.ts +4 -3
- package/dist/hosted.js +16 -12
- package/dist/hosted.mjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.mts +39 -8
- package/dist/index.d.ts +39 -8
- package/dist/index.js +14 -10
- package/dist/index.mjs +1 -1
- package/dist/materialVariants-DnZe9eMC.d.mts +309 -0
- package/dist/materialVariants-DnZe9eMC.d.ts +309 -0
- package/dist/modelInfo-BYebMPdZ.d.mts +43 -0
- package/dist/modelInfo-XfUQO7C_.d.ts +43 -0
- package/dist/{ModelViewer-Bj-KjiDL.d.mts → modelViewerProps-CIZ2R_Fv.d.ts} +82 -10
- package/dist/{ModelViewer-BkQ73y6N.d.ts → modelViewerProps-DoRiTZMn.d.mts} +82 -10
- package/dist/{objectCatalog-BIeXURoq.d.ts → rendererBackend-QwMzISTn.d.mts} +32 -4
- package/dist/{objectCatalog-BIeXURoq.d.mts → rendererBackend-QwMzISTn.d.ts} +32 -4
- package/dist/simple-model-viewer.css +1 -1
- package/dist/simple-model-viewer.d.mts +32 -4
- package/dist/simple-model-viewer.d.ts +32 -4
- package/dist/simple-model-viewer.js +3 -3
- package/dist/simple-model-viewer.mjs +1 -1
- package/dist/utils.d.mts +5 -4
- package/dist/utils.d.ts +5 -4
- package/dist/utils.js +1 -1
- package/dist/utils.mjs +1 -1
- package/package.json +2 -1
- package/dist/chunk-32WCLPTZ.mjs +0 -1
- package/dist/chunk-CSGNL4XU.mjs +0 -3
- package/dist/chunk-F2ILTIPG.mjs +0 -28
- package/dist/chunk-V6GALFL5.mjs +0 -1
- package/dist/modelInfo-DnkmTGMd.d.mts +0 -23
- package/dist/modelInfo-DnkmTGMd.d.ts +0 -23
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# React Immersive — 3D model viewer for React
|
|
2
2
|
|
|
3
|
-
`@liveroom-tech/react-immersive` is a React-based 3D model viewer for interactive GLB, GLTF, OBJ, FBX, and
|
|
3
|
+
`@liveroom-tech/react-immersive` is a React-based 3D model viewer for interactive GLB, GLTF, OBJ, FBX, USDZ, STL, and PLY assets. It renders a model with React Three Fiber, lets users click named meshes, shows a built-in side panel for the selected object, and supports per-object actions such as color changes, texture uploads, and visibility toggles.
|
|
4
4
|
|
|
5
5
|
[Website](https://react-immersive.liveroom.dev) · [Documentation](https://react-immersive.liveroom.dev/docs) · [Live editor](https://react-immersive.liveroom.dev/editor) · [npm](https://www.npmjs.com/package/@liveroom-tech/react-immersive) · [GitHub](https://github.com/Liveroom-Technologies/react-immersive-docs)
|
|
6
6
|
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
useObjectBindingIds,
|
|
19
19
|
useObjectBindings,
|
|
20
20
|
useSceneConfig,
|
|
21
|
+
useShareableViewerState,
|
|
21
22
|
useViewer,
|
|
22
23
|
useViewerActions,
|
|
23
24
|
useViewerAnimations,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
useViewerConnection,
|
|
26
27
|
useViewerEffects,
|
|
27
28
|
useViewerHover,
|
|
29
|
+
useViewerIsolation,
|
|
28
30
|
useViewerModel,
|
|
29
31
|
useViewerSelection,
|
|
30
32
|
MATERIAL_BLENDING_MODES,
|
|
@@ -41,7 +43,7 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
41
43
|
|
|
42
44
|
`ModelViewer` provides:
|
|
43
45
|
|
|
44
|
-
- GLB/GLTF, OBJ/MTL, FBX, and
|
|
46
|
+
- GLB/GLTF, OBJ/MTL, FBX, USDZ, STL, and PLY model rendering through `@react-three/fiber`
|
|
45
47
|
- camera-driven 3D Tiles streaming through `tilesetUrl`, with configurable screen-space error and bounded tile cache
|
|
46
48
|
- orbit/pan/zoom camera controls via `CameraControls`
|
|
47
49
|
- auto-fits the model on load, and re-fits when the canvas is resized (window resize, device rotation, or a panel opening/closing), toggleable through `refitOnResize` to instead preserve the user's orbit/zoom
|
|
@@ -51,6 +53,7 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
51
53
|
- optional shadow rendering toggle through a `shadows` prop (on by default)
|
|
52
54
|
- optional on-screen movement controller through `showMouseController` and its tuning props
|
|
53
55
|
- optional canvas-focused keyboard camera navigation through `enableKeyboardNavigation`
|
|
56
|
+
- keyboard object navigation, screen-reader selection announcements, and reduced motion for visitors who ask for it (see [Accessibility](#accessibility))
|
|
54
57
|
- optional top-right camera orientation control through `showViewGizmo`
|
|
55
58
|
- optional per-object move and rotate gizmo through `moveModeEnabled`, with World/Local axis alignment through `objectTransformSpace`
|
|
56
59
|
- optional custom left object data panel through `customObjectBindingDataPanel`
|
|
@@ -59,18 +62,21 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
59
62
|
- optional hiding of either built-in panel through `showObjectBindingDataPanel` and `showSceneObjectsPanel`
|
|
60
63
|
- optional model export button through `showDownloadButton` and `downloadFilename`, opt-in PNG capture through `preserveDrawingBuffer`, plus an independent `showResetButton` toggle for the rest of that action bar
|
|
61
64
|
- an optional UV Checker toolbar button through `showUvCheckerButton`, for inspecting UV scale, stretching, seams, orientation, and missing UV0 coordinates directly in `ModelViewer`
|
|
62
|
-
- glTF material variants (`KHR_materials_variants`)
|
|
65
|
+
- glTF material variants (`KHR_materials_variants`) as swatch rows, one per option group, through `showMaterialVariants`, selected by `sceneConfig.model.activeVariants`
|
|
63
66
|
- distance, angle, polygon area, and point-to-plane measurements with vertex/edge snapping and bounding dimensions through `showMeasureTools` and `measurementUnit`
|
|
64
67
|
- an exploded-view slider that slides each bound part outward from the model center to reveal interior/assembly structure through `showExplodeControls`
|
|
65
68
|
- isolation that ghosts or hides everything but the objects you focus on, through `isolation` / `onIsolationChange` and `showIsolateControls`
|
|
66
69
|
- a section cut that opens the model along an axis, with a draggable plane, through `showSectionTools` and `sceneConfig.section`
|
|
67
70
|
- a cinematic auto-camera that glides the camera along authored waypoints (or a zero-config showcase orbit), like a film, through `cinematic`, with a play/pause control that yields the moment you touch the camera
|
|
68
|
-
- video export of a cinematic pass or turntable revolution, through the download menu or `captureVideo`,
|
|
71
|
+
- video export of a cinematic pass or turntable revolution, through the download menu or `captureVideo`, as browser-supported WebM/MP4 or an animated GIF
|
|
72
|
+
- `CompareViewer`, which shows two configurations (or two models) with one camera, as a before/after slider or side by side
|
|
73
|
+
- opt-in WebGPU rendering through `perf={{ webgpu: true }}`, falling back to WebGL where the browser lacks it
|
|
74
|
+
- lazy loading: a viewer starts its 3D view when it's near the screen, and a page of viewers stays within the browser's limit on 3D views, with a `poster` image and an optional "View in 3D" button (`reveal`) until the view is there (see [Lazy loading and posters](#lazy-loading-and-posters))
|
|
69
75
|
- a guided-tour control cluster (Previous/Stop/Next) for stepping through annotations, toggleable via `showAnnotationNavigation`
|
|
70
76
|
- optional annotation detail popups on marker hover via `showAnnotationOnHover`
|
|
71
77
|
- PBR, Matcap, and UV Checker renderer modes, with the checker exposing UV stretching, seams, rotation, and missing UV0 data directly on the model
|
|
72
78
|
- a `sceneConfig` prop accepting the same scene-wide config (lighting, environment, background, post-processing, animations, annotations) authored by `BindingBuilder`'s Scene tab
|
|
73
|
-
- WebGL context-loss recovery with a reload button and automatic rebuild when the context returns
|
|
79
|
+
- WebGL context-loss recovery with a reload button and automatic rebuild when the context returns, keeping the camera and the visitor's changes
|
|
74
80
|
- render-loop and perf tuning through `renderMode`, `maxDpr`, `performanceProfile`, and compressed-asset decoder options (`dracoDecoderPath`, `ktx2TranscoderPath`, `meshopt`)
|
|
75
81
|
- WebXR "View in your space" (AR) and "Enter VR" through `enableXR`, with tap-to-place, pinch-to-resize, and twist-to-rotate AR placement gestures
|
|
76
82
|
- mesh selection by object name
|
|
@@ -97,8 +103,8 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
97
103
|
|
|
98
104
|
`BindingBuilder` provides:
|
|
99
105
|
|
|
100
|
-
- a design-time UI for generating starter bindings from GLB, GLTF, OBJ, FBX, and
|
|
101
|
-
- demo model loading or custom `.glb`, `.gltf`, `.obj`, `.fbx`, `.usdz`, or model ZIP bundle upload
|
|
106
|
+
- a design-time UI for generating starter bindings from GLB, GLTF, OBJ, FBX, USDZ, STL, and PLY assets, gated by `licenseKey` (see "`BindingBuilder` licensing & plan tiers" below)
|
|
107
|
+
- demo model loading or custom `.glb`, `.gltf`, `.obj`, `.fbx`, `.usdz`, `.stl`, `.ply`, or model ZIP bundle upload
|
|
102
108
|
- editable binding fields for identity, basics, style, actions, metrics, metadata, and saved camera state
|
|
103
109
|
- per-object move and rotate authoring with a geometry-centered pivot, World/Local axes, numeric fields, and restore-to-authored-transform support
|
|
104
110
|
- a one-click material preset gallery (wood, metal, chrome, glass, plastic, fabric, ceramic, concrete, etc.)
|
|
@@ -112,8 +118,8 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
112
118
|
|
|
113
119
|
`SimpleModelViewer` provides:
|
|
114
120
|
|
|
115
|
-
- a lightweight GLB/GLTF/OBJ/FBX/USDZ viewer with no bindings or actions that does not require a `licenseKey`
|
|
116
|
-
- optional local `.glb`, standalone/data-URI `.gltf`, `.obj`, `.fbx`, `.usdz`, or model ZIP bundle upload mode for ad-hoc inspection without relying on the `modelUrl` asset
|
|
121
|
+
- a lightweight GLB/GLTF/OBJ/FBX/USDZ/STL/PLY viewer with no bindings or actions that does not require a `licenseKey`
|
|
122
|
+
- optional local `.glb`, standalone/data-URI `.gltf`, `.obj`, `.fbx`, `.usdz`, `.stl`, `.ply`, or model ZIP bundle upload mode for ad-hoc inspection without relying on the `modelUrl` asset
|
|
117
123
|
- built-in scene objects panel with search and visibility toggles
|
|
118
124
|
- a built-in environment/scene settings panel (background color, environment preset, auto-rotate, exposure, ambient + directional light) toggleable via `showSceneSettingsPanel`, with each control's initial value seedable via props
|
|
119
125
|
- click-to-select and fit-to-object focus behavior
|
|
@@ -763,8 +769,9 @@ type BindingBuilderProps = {
|
|
|
763
769
|
|
|
764
770
|
Current behavior, Object tab:
|
|
765
771
|
|
|
766
|
-
- upload a `.glb`, standalone/data-URI `.gltf`, `.obj`, `.fbx`, or `.
|
|
772
|
+
- upload a `.glb`, standalone/data-URI `.gltf`, `.obj`, `.fbx`, `.usdz`, `.stl`, or `.ply` file, or a `.zip` bundle (a `.gltf` with its `.bin` and textures, an `.obj` with its MTL and textures, or an `.fbx` with external textures)
|
|
767
773
|
- traverse renderable mesh nodes and generate starter bindings automatically
|
|
774
|
+
- for an object without texture coordinates (STL, most PLY), the **Material** image map fields are turned off with a note, and the starter actions leave out **Change Material** (see [STL and PLY](#stl-and-ply))
|
|
768
775
|
- edit binding identity, label, type, status, booleans, style, actions, metrics, metadata, and saved camera state
|
|
769
776
|
- add decals to the selected object from the **Decals** panel: upload an image or add text to place it automatically at the centre of the camera-facing surface, then set its size, rotation, and opacity; **Move** lets you drag the decal across the object's surface. Release to save, or press Escape to cancel. Uploaded images are exported under `materials/` with the bindings
|
|
770
777
|
- move and rotate the selected object from the **Move & Rotate** panel using a geometry-centered pivot, World/Local axis alignment, or numeric position and degree fields; restore its authored transform with one click
|
|
@@ -778,7 +785,8 @@ Current behavior, Scene tab:
|
|
|
778
785
|
- configure the same `SceneConfig` shape `ModelViewer`'s `sceneConfig` prop accepts (lighting, environment, background, ground shadows, wireframe, post-processing, animations, annotations)
|
|
779
786
|
- configure auto-rotation and its speed, exported as `sceneConfig.model.autoRotate` and `sceneConfig.model.autoRotateSpeed`
|
|
780
787
|
- preview animation clips, several at once and forwards or backwards, and set each clip's loop mode (loop, play once, or ping-pong), speed, and display name
|
|
781
|
-
-
|
|
788
|
+
- check changes with **Compare** above the preview: a before/after divider across the preview, with the project as it was opened on the left and as it is now on the right; orbiting either side moves both, and **Pin** on the "Before" label makes the current state the "before" for the next round of changes
|
|
789
|
+
- pick which of the model's glTF material variants the scene starts with, one per option group, exported as `sceneConfig.model.activeVariants`
|
|
782
790
|
- isolate a node from the Scene Nodes list to see it on its own in the preview while you edit it (not exported)
|
|
783
791
|
- set up a section cut (**General → Section Cut**: axis, position, and side to keep), exported as `sceneConfig.section`; the preview has the section toolbar and draggable plane too
|
|
784
792
|
- build style rules (**Rules** sub-tab) that restyle objects from their metrics, status, and metadata, with suggestions from the model's bindings, a live match count, and a live preview; exported as `sceneConfig.styleRules`
|
|
@@ -794,7 +802,7 @@ Preview:
|
|
|
794
802
|
Autosave:
|
|
795
803
|
|
|
796
804
|
- the session (the model, its object bindings, and the scene settings, with an uploaded model file included) is saved to this browser's IndexedDB, in a database named `react-immersive:binding-builder`, shortly after each edit and when the tab is hidden or closed
|
|
797
|
-
- **Restore last session** in the preview
|
|
805
|
+
- **Restore last session** in the preview's "⋯" menu reopens it; one session is kept per browser
|
|
798
806
|
- while there are edits on screen, leaving or reloading the page asks for confirmation
|
|
799
807
|
- if the browser refuses to store a session, usually for lack of space, the editor says so; export your work to keep it
|
|
800
808
|
- enable **Transform gizmo**, select a mesh, then use the arrows and plane handles to move it or the rings to rotate it; all changes are saved to `binding.transform`
|
|
@@ -813,7 +821,7 @@ A locked feature renders in place (not hidden) with a message naming the require
|
|
|
813
821
|
|
|
814
822
|
## `SimpleModelViewer`
|
|
815
823
|
|
|
816
|
-
`SimpleModelViewer` is an exported lightweight viewer for cases where you want to load a GLB, GLTF, OBJ, FBX, or
|
|
824
|
+
`SimpleModelViewer` is an exported lightweight viewer for cases where you want to load a GLB, GLTF, OBJ, FBX, USDZ, STL, or PLY asset, inspect meshes, and toggle visibility without building `objectBindings`. It can either load a fixed `modelUrl` or, when `enableModelUpload` is turned on, let the user drag-and-drop or choose a local model at runtime.
|
|
817
825
|
|
|
818
826
|
Basic usage:
|
|
819
827
|
|
|
@@ -900,6 +908,60 @@ const {
|
|
|
900
908
|
|
|
901
909
|
Pass `initialPosition`, `initialTarget`, and optional named `presets` to `useViewerCamera` to configure the first view without writing an `onViewerReady` wrapper.
|
|
902
910
|
|
|
911
|
+
If you want a link that opens the viewer the way it looks now, the library also exports `useShareableViewerState`. It keeps the camera, the hidden objects, and the selection in the page URL's hash (`#view=…`), and puts them back when the link is opened:
|
|
912
|
+
|
|
913
|
+
```tsx
|
|
914
|
+
const viewer = useViewer();
|
|
915
|
+
const share = useShareableViewerState({
|
|
916
|
+
cameraState: viewer.camera.cameraState,
|
|
917
|
+
setCameraState: viewer.camera.setCameraState,
|
|
918
|
+
objectBindings: viewer.bindings.objectBindings,
|
|
919
|
+
onObjectBindingsChange: viewer.bindings.setObjectBindings,
|
|
920
|
+
selectedObjectBinding: viewer.selection.selectedObjectBinding,
|
|
921
|
+
setSelectedObjectBinding: viewer.selection.setSelectedObjectBinding,
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
<ModelViewer
|
|
925
|
+
modelUrl="/model.glb"
|
|
926
|
+
licenseKey="your-license-key"
|
|
927
|
+
{...viewer.props}
|
|
928
|
+
onViewerReady={(ready) => {
|
|
929
|
+
viewer.props.onViewerReady(ready);
|
|
930
|
+
void share.restoreFromUrl();
|
|
931
|
+
}}
|
|
932
|
+
refitOnResize={false}
|
|
933
|
+
/>;
|
|
934
|
+
|
|
935
|
+
<button onClick={() => navigator.clipboard.writeText(share.getShareUrl())}>
|
|
936
|
+
Copy link
|
|
937
|
+
</button>;
|
|
938
|
+
```
|
|
939
|
+
|
|
940
|
+
`useShareableViewerState` takes:
|
|
941
|
+
|
|
942
|
+
- `cameraState` and `setCameraState`: from `useViewerCamera` (`viewer.camera`)
|
|
943
|
+
- `objectBindings` and `onObjectBindingsChange`: the bindings and their setter; hidden objects are restored by setting their `visible` to `false`
|
|
944
|
+
- `selectedObjectBinding` and `setSelectedObjectBinding`: from `useViewerSelection` (`viewer.selection`). `ModelViewer` also needs `selectedObject` (`viewer.props` passes it), or a restored selection won't show
|
|
945
|
+
- `paramKey`: the hash key the view is stored under. Default `"view"`
|
|
946
|
+
- `debounceMs`: how long after a change the hash is updated, in milliseconds. Default `400`
|
|
947
|
+
- `autoSync`: keep the hash up to date as the view changes. Default `true`. With `false`, write it with `syncUrl` or build links with `getShareUrl`
|
|
948
|
+
|
|
949
|
+
`useShareableViewerState` returns:
|
|
950
|
+
|
|
951
|
+
- `getShareUrl`: the current page URL with the current view in its hash, without changing the address bar
|
|
952
|
+
- `syncUrl`: writes the current view into the hash now, with `history.replaceState`, so no history entry is added
|
|
953
|
+
- `restoreFromUrl`: applies the view in the hash, if there is one, and resolves `true` once it has applied all of it. Called again, it does nothing until the hash holds a different view, so it's safe to call from `onViewerReady`, which runs often
|
|
954
|
+
|
|
955
|
+
Things to know:
|
|
956
|
+
|
|
957
|
+
- The link holds the view, not the model. Keep whatever identifies the model, such as a route or query parameter, in the URL too. Objects are stored by `binding.id`, so links keep working while the ids stay the same.
|
|
958
|
+
- The hash is only updated after `restoreFromUrl` has been called once, so an incoming link isn't overwritten before it's read. Call it even if you don't restore links; with nothing in the hash it does nothing.
|
|
959
|
+
- When the bindings come from the model, as with `useViewer` or `objectBindings="auto"`, they exist only once it has loaded, after the first `onViewerReady`. A link that hides or selects objects then restores its camera at once and waits for them: `onViewerReady` runs again when the bindings arrive, and that call hides and selects them. The hash isn't updated while it waits.
|
|
960
|
+
- The camera jumps to the shared view without a transition. `refitOnResize={false}` keeps a later resize from re-framing the model away from it.
|
|
961
|
+
- The hook listens for `hashchange`, so pasting a newer link into an open tab applies it.
|
|
962
|
+
|
|
963
|
+
See the [Shareable View example](https://react-immersive.liveroom.dev/examples/shareable-view) and the [`useShareableViewerState` docs](https://react-immersive.liveroom.dev/docs/hooks/use-shareable-viewer-state).
|
|
964
|
+
|
|
903
965
|
If your GLB/GLTF model contains animations, the library also exports:
|
|
904
966
|
|
|
905
967
|
```tsx
|
|
@@ -1000,6 +1062,90 @@ stays open. `"ping-pong"` plays forwards then backwards, repeatedly. Playing a
|
|
|
1000
1062
|
clip that's already playing in the same direction leaves it running; playing
|
|
1001
1063
|
it the other way turns it around where it is.
|
|
1002
1064
|
|
|
1065
|
+
## `CompareViewer`
|
|
1066
|
+
|
|
1067
|
+
Shows two configurations next to each other with one camera: a before and
|
|
1068
|
+
after of a renovation, two paint options, the current part against its
|
|
1069
|
+
replacement. Each side is a full [`ModelViewer`](#public-api),
|
|
1070
|
+
so a side can differ in anything a viewer takes: `objectBindings`,
|
|
1071
|
+
`sceneConfig`, material variants, even a different `modelUrl`.
|
|
1072
|
+
|
|
1073
|
+
```tsx
|
|
1074
|
+
import "@liveroom-tech/react-immersive/styles.css";
|
|
1075
|
+
import { CompareViewer } from "@liveroom-tech/react-immersive";
|
|
1076
|
+
|
|
1077
|
+
<div style={{ height: 600 }}>
|
|
1078
|
+
<CompareViewer
|
|
1079
|
+
modelUrl="/kitchen.glb"
|
|
1080
|
+
licenseKey="your-license-key"
|
|
1081
|
+
before={{ label: "Current", objectBindings: currentBindings }}
|
|
1082
|
+
after={{ label: "Proposed", objectBindings: proposedBindings }}
|
|
1083
|
+
/>
|
|
1084
|
+
</div>;
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
### Layouts
|
|
1088
|
+
|
|
1089
|
+
- **`layout="slider"`** (default) lays the two views over each other. A divider
|
|
1090
|
+
splits them: the left of it shows `before`, the right shows `after`. Drag the
|
|
1091
|
+
divider, or focus it and use the arrow keys, Page Up/Down, Home, and End. It
|
|
1092
|
+
is a slider to screen readers, with each side's label and share.
|
|
1093
|
+
- **`layout="side-by-side"`** shows the two views next to each other, each half
|
|
1094
|
+
the width.
|
|
1095
|
+
|
|
1096
|
+
```tsx
|
|
1097
|
+
<CompareViewer
|
|
1098
|
+
modelUrl="/car.glb"
|
|
1099
|
+
licenseKey="your-license-key"
|
|
1100
|
+
layout="side-by-side"
|
|
1101
|
+
before={{ sceneConfig: { ...config, model: { ...config.model, activeVariants: ["Paint: Red"] } } }}
|
|
1102
|
+
after={{ sceneConfig: { ...config, model: { ...config.model, activeVariants: ["Paint: Blue"] } } }}
|
|
1103
|
+
/>
|
|
1104
|
+
```
|
|
1105
|
+
|
|
1106
|
+
### One camera
|
|
1107
|
+
|
|
1108
|
+
Orbiting, panning, or zooming either side moves both. The side you last
|
|
1109
|
+
grabbed leads and the other copies it every frame, so a fly-in, a zoom to a
|
|
1110
|
+
selected object, or a refit on one side shows on both. With two different
|
|
1111
|
+
models, the views line up on the same point in space, so the models should
|
|
1112
|
+
share a coordinate system.
|
|
1113
|
+
|
|
1114
|
+
### Props
|
|
1115
|
+
|
|
1116
|
+
```ts
|
|
1117
|
+
type CompareViewerProps = Partial<ModelViewerProps> & {
|
|
1118
|
+
licenseKey: string;
|
|
1119
|
+
before: CompareViewerSide; // the left side
|
|
1120
|
+
after: CompareViewerSide; // the right side
|
|
1121
|
+
layout?: "slider" | "side-by-side"; // default "slider"
|
|
1122
|
+
split?: number; // the divider, 0 (left edge) to 1; default 0.5
|
|
1123
|
+
onSplitChange?: (split: number) => void;
|
|
1124
|
+
};
|
|
1125
|
+
|
|
1126
|
+
type CompareViewerSide = Partial<ModelViewerProps> & {
|
|
1127
|
+
label?: string; // shown over the side; default "Before" / "After"
|
|
1128
|
+
};
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
Every other prop is shared by both sides, and a side's own props win.
|
|
1132
|
+
Callbacks run per side: give `before.onObjectSelect` and
|
|
1133
|
+
`after.onObjectSelect` to tell the sides apart. Both sides start from the
|
|
1134
|
+
`minimal` [preset](#presets-and-grouped-props),
|
|
1135
|
+
with no panels or buttons, so their interfaces don't overlap; pass `preset`
|
|
1136
|
+
or `ui` to change that. Pass `split` to control the divider yourself;
|
|
1137
|
+
without it, `CompareViewer` keeps the position.
|
|
1138
|
+
|
|
1139
|
+
> Each side has its own WebGL (or [WebGPU](#webgpu)) canvas. The two share one download and parse of the model file, but the GPU holds two copies of its geometry and textures.
|
|
1140
|
+
|
|
1141
|
+
`poster`, `loading` and `reveal` work as on
|
|
1142
|
+
[`ModelViewer`](#lazy-loading-and-posters). With `reveal="interaction"`, one
|
|
1143
|
+
**View in 3D** button starts both sides.
|
|
1144
|
+
|
|
1145
|
+
The divider and the default side labels use the
|
|
1146
|
+
[`labels`](https://react-immersive.liveroom.dev/docs/reference/labels) keys `compareBefore`, `compareAfter`,
|
|
1147
|
+
`compareDivider`, and `compareDividerValue`.
|
|
1148
|
+
|
|
1003
1149
|
## Presets and grouped props
|
|
1004
1150
|
|
|
1005
1151
|
`preset` sets `ModelViewer` up for a common kind of page:
|
|
@@ -1031,10 +1177,10 @@ readable:
|
|
|
1031
1177
|
|
|
1032
1178
|
| Group | Fields, and the flat prop each one sets |
|
|
1033
1179
|
| --- | --- |
|
|
1034
|
-
| `ui` | `objectPanel` (`showObjectBindingDataPanel`), `sceneObjects` (`showSceneObjectsPanel`), `download` (`showDownloadButton`; a string also sets `downloadFilename`), `reset` (`showResetButton`), `fullscreen` (`showFullscreenButton`), `loadingOverlay` (`showLoadingOverlay`), `viewGizmo` (`showViewGizmo`), `mouseController` (`showMouseController`; `{ position, opacity }` also sets `mouseControllerPosition` and `mouseControllerOpacity`), `annotationNavigation` (`showAnnotationNavigation`), `annotationOnHover` (`showAnnotationOnHover`), `highlightOnHover`, `materialVariants` (`showMaterialVariants`) |
|
|
1180
|
+
| `ui` | `objectPanel` (`showObjectBindingDataPanel`), `sceneObjects` (`showSceneObjectsPanel`), `download` (`showDownloadButton`; a string also sets `downloadFilename`), `reset` (`showResetButton`), `fullscreen` (`showFullscreenButton`), `loadingOverlay` (`showLoadingOverlay`), `viewGizmo` (`showViewGizmo`), `mouseController` (`showMouseController`; `{ position, opacity }` also sets `mouseControllerPosition` and `mouseControllerOpacity`), `annotationNavigation` (`showAnnotationNavigation`), `annotationOnHover` (`showAnnotationOnHover`), `highlightOnHover`, `materialVariants` (`showMaterialVariants`), `objectNavigator` (`showObjectNavigator`) |
|
|
1035
1181
|
| `tools` | `measure` (`showMeasureTools`; `{ unit }` also sets `measurementUnit`), `uvChecker` (`showUvCheckerButton`), `explode` (`showExplodeControls`), `section` (`showSectionTools`), `isolate` (`showIsolateControls`), `texturePositioning` (`texturePositioning`) |
|
|
1036
|
-
| `controls` | `enabled` (`enableCameraControls`), `keyboard` (`enableKeyboardNavigation`), `zoom` (the opposite of `disableZoom`), `zoomOnSelect` (`zoomOnSelected`), `autoRotate` (a number also sets `autoRotateSpeed`), `refitOnResize`, `limits` (`cameraLimits`), `moveSensitivity`, `zoomSensitivity` |
|
|
1037
|
-
| `perf` | `renderMode`, `maxDpr`, `profile` (`performanceProfile`), `preserveDrawingBuffer` |
|
|
1182
|
+
| `controls` | `enabled` (`enableCameraControls`), `keyboard` (`enableKeyboardNavigation`), `zoom` (the opposite of `disableZoom`), `zoomOnSelect` (`zoomOnSelected`), `autoRotate` (a number also sets `autoRotateSpeed`), `refitOnResize`, `limits` (`cameraLimits`), `moveSensitivity`, `zoomSensitivity`, `reducedMotion` |
|
|
1183
|
+
| `perf` | `renderMode`, `maxDpr`, `profile` (`performanceProfile`), `preserveDrawingBuffer`, `webgpu` |
|
|
1038
1184
|
| `tiles` | `url` (`tilesetUrl`), `errorTarget` (`tilesErrorTarget`), `cacheSize` (`tilesCacheSize`) |
|
|
1039
1185
|
| `xr` | `enabled` (`enableXR`), `usdzUrl`, `scaleMode` (`arScaleMode`), `mobileHandoffUrl` |
|
|
1040
1186
|
| `decoders` | `draco` (`dracoDecoderPath`), `ktx2` (`ktx2TranscoderPath`), `meshopt` |
|
|
@@ -1057,11 +1203,14 @@ type Props = {
|
|
|
1057
1203
|
xr?: ModelViewerXrOptions;
|
|
1058
1204
|
decoders?: ModelViewerDecoderOptions;
|
|
1059
1205
|
modelUrl: string;
|
|
1206
|
+
poster?: string;
|
|
1207
|
+
loading?: "lazy" | "eager"; // default "lazy"
|
|
1208
|
+
reveal?: "auto" | "interaction"; // default "auto"
|
|
1060
1209
|
tilesetUrl?: string | null;
|
|
1061
1210
|
tilesErrorTarget?: number;
|
|
1062
1211
|
tilesCacheSize?: number;
|
|
1063
1212
|
onObjectCatalog?: (objects: ViewerObjectInfo[]) => void;
|
|
1064
|
-
modelFormat?: "glb" | "gltf" | "obj" | "fbx" | "usdz";
|
|
1213
|
+
modelFormat?: "glb" | "gltf" | "obj" | "fbx" | "usdz" | "stl" | "ply";
|
|
1065
1214
|
licenseKey: string;
|
|
1066
1215
|
objectBindings?: Record<string, ObjectBinding> | "auto"; // default "auto"
|
|
1067
1216
|
selectedObject?: ObjectBinding | null;
|
|
@@ -1127,11 +1276,14 @@ type Props = {
|
|
|
1127
1276
|
moveModeEnabled?: boolean;
|
|
1128
1277
|
objectTransformSpace?: "local" | "world";
|
|
1129
1278
|
enableKeyboardNavigation?: boolean;
|
|
1279
|
+
showObjectNavigator?: boolean;
|
|
1280
|
+
reducedMotion?: "user" | "always" | "never";
|
|
1130
1281
|
onAutoFit?: () => Promise<boolean>;
|
|
1131
1282
|
refitOnResize?: boolean;
|
|
1132
1283
|
renderMode?: "always" | "demand";
|
|
1133
1284
|
maxDpr?: number;
|
|
1134
1285
|
preserveDrawingBuffer?: boolean;
|
|
1286
|
+
webgpu?: boolean;
|
|
1135
1287
|
performanceProfile?: "auto" | "high" | "low";
|
|
1136
1288
|
dracoDecoderPath?: string | false;
|
|
1137
1289
|
ktx2TranscoderPath?: string | false;
|
|
@@ -1163,7 +1315,7 @@ type Props = {
|
|
|
1163
1315
|
|
|
1164
1316
|
### `modelUrl`
|
|
1165
1317
|
|
|
1166
|
-
URL or public path to a `.glb`, `.gltf`, `.obj`, `.fbx`, or `.
|
|
1318
|
+
URL or public path to a `.glb`, `.gltf`, `.obj`, `.fbx`, `.usdz`, `.stl`, or `.ply` model. Hosted GLTF, OBJ, and FBX assets must serve external buffers, MTL files, and textures at their declared relative paths. USDZ dependencies are contained in the USDZ package. Use `modelFormat` when a signed or extensionless URL cannot be auto-detected.
|
|
1167
1319
|
|
|
1168
1320
|
Example:
|
|
1169
1321
|
|
|
@@ -1174,7 +1326,33 @@ modelUrl = "/model.glb";
|
|
|
1174
1326
|
### `modelFormat`
|
|
1175
1327
|
|
|
1176
1328
|
Optional explicit format for signed or extensionless model URLs. Normal URLs
|
|
1177
|
-
ending in `.glb`, `.gltf`, `.obj`, `.fbx`, or `.
|
|
1329
|
+
ending in `.glb`, `.gltf`, `.obj`, `.fbx`, `.usdz`, `.stl`, or `.ply` are detected automatically.
|
|
1330
|
+
|
|
1331
|
+
### STL and PLY
|
|
1332
|
+
|
|
1333
|
+
STL and PLY files hold geometry only: no object names, materials or
|
|
1334
|
+
animations.
|
|
1335
|
+
|
|
1336
|
+
- **One object per file**, named after the file: `bracket.stl` loads as one
|
|
1337
|
+
object keyed `bracket`, which its bindings use. A text STL with several named
|
|
1338
|
+
solids loads one object per solid. A `.zip` holding one STL or PLY file also
|
|
1339
|
+
works.
|
|
1340
|
+
- **Colour:** objects start a matte grey. A PLY file's vertex colours show as
|
|
1341
|
+
they are, and picking a colour (or texture) replaces them.
|
|
1342
|
+
- **No texture coordinates:** STL files have none and PLY files rarely do, so an
|
|
1343
|
+
image can't be mapped onto them. The viewer's **Change Material** says so
|
|
1344
|
+
instead of offering an upload, BindingBuilder turns off its image map fields
|
|
1345
|
+
for that object, and new bindings leave Change Material out. [Decals](#decals)
|
|
1346
|
+
work without texture coordinates. This applies to any object without them, in
|
|
1347
|
+
any format.
|
|
1348
|
+
- **Units and orientation:** STL files are usually in millimetres with Z up, so a
|
|
1349
|
+
part can load lying on its back. Turn it upright with
|
|
1350
|
+
[`sceneConfig.model.rotation`](#sceneconfig) (BindingBuilder: **Scene** →
|
|
1351
|
+
model rotation, −90° on X), and set [`measurementUnit`](#measurementunit) to
|
|
1352
|
+
`"mm"`. In AR, the default `"normalized"` [`arScaleMode`](#arscalemode) fits the
|
|
1353
|
+
model to a metre; `"real-world"` would read millimetres as metres.
|
|
1354
|
+
- **PLY point clouds** (points with no faces) can't be shown; the viewer
|
|
1355
|
+
explains why instead of loading them.
|
|
1178
1356
|
|
|
1179
1357
|
### 3D Tiles streaming
|
|
1180
1358
|
|
|
@@ -1463,6 +1641,7 @@ type ViewerModelInfo = {
|
|
|
1463
1641
|
bounds: { min: Vec3; max: Vec3; center: Vec3; size: Vec3 }; // world space
|
|
1464
1642
|
objectCount: number; // meshes in the loaded scene
|
|
1465
1643
|
materialVariants: string[]; // glTF material variant names, if any
|
|
1644
|
+
materialVariantGroups: MaterialVariantGroup[]; // the same, as option groups
|
|
1466
1645
|
};
|
|
1467
1646
|
```
|
|
1468
1647
|
|
|
@@ -1546,8 +1725,8 @@ elapsed time includes loading and background time; it is not an attention metric
|
|
|
1546
1725
|
|
|
1547
1726
|
| Event `type` | Details and trigger |
|
|
1548
1727
|
| --- | --- |
|
|
1549
|
-
| `part-viewed` | `objectId`, `objectKey`, optional `label`, and `source` (`"canvas"` or `"
|
|
1550
|
-
| `variant-chosen` | `variant
|
|
1728
|
+
| `part-viewed` | `objectId`, `objectKey`, optional `label`, and `source` (`"canvas"`, `"panel"`, or `"keyboard"`). Selecting/focusing a different part; reselecting the current part is ignored. |
|
|
1729
|
+
| `variant-chosen` | `variant`, `previousVariant` (names, or `null` for the default), and `group` (the option group's name, or `null`). Choosing or clearing a built-in material variant; `previousVariant` is the earlier choice in the same group. |
|
|
1551
1730
|
| `ar-started` | A confirmed immersive WebXR AR session begins. |
|
|
1552
1731
|
| `ar-ended` | `durationMs` and `reason` (`"session-ended"` or `"interrupted"`). A confirmed AR session ends, or tracking stops on model change, unmount, or context loss. |
|
|
1553
1732
|
| `ar-launch-requested` | `channel` (`"quick-look"` or `"mobile-handoff"`). Opening external AR or its QR handoff; this does not claim an AR session started. |
|
|
@@ -1585,6 +1764,7 @@ type ViewerReadyState = {
|
|
|
1585
1764
|
captureImage: (options?: CaptureImageOptions) => Promise<string>;
|
|
1586
1765
|
captureVideo: (options?: CaptureVideoOptions) => Promise<Blob>;
|
|
1587
1766
|
invalidate: () => void;
|
|
1767
|
+
rendererBackend: "webgl" | "webgpu"; // which renderer draws; see `webgpu`
|
|
1588
1768
|
raw: {
|
|
1589
1769
|
controls: CameraControls; // camera-controls
|
|
1590
1770
|
scene: Object3D | null; // three.js
|
|
@@ -1651,7 +1831,7 @@ const abort = new AbortController();
|
|
|
1651
1831
|
// Run in an event handler. Upload the Blob or preview it with an object URL.
|
|
1652
1832
|
const clip = await viewer.connection.captureVideo({
|
|
1653
1833
|
mode: "turntable", // or "cinematic" to use the enabled camera path
|
|
1654
|
-
format: "auto", // prefers WebM; falls back to MP4
|
|
1834
|
+
format: "auto", // prefers WebM; falls back to MP4. "gif" for an animated GIF
|
|
1655
1835
|
duration: 12, // seconds; defaults to path timing or a 24-second orbit
|
|
1656
1836
|
fps: 30, // 1–60; actual throughput depends on rendering/encoding
|
|
1657
1837
|
videoBitsPerSecond: 8_000_000,
|
|
@@ -1661,7 +1841,7 @@ const clip = await viewer.connection.captureVideo({
|
|
|
1661
1841
|
```
|
|
1662
1842
|
|
|
1663
1843
|
The download menu offers **Record turntable video** and, when cinematic is
|
|
1664
|
-
enabled, **Record cinematic video**, plus Auto/WebM/MP4 format selection. A
|
|
1844
|
+
enabled, **Record cinematic video**, plus Auto/WebM/MP4/GIF format selection. A
|
|
1665
1845
|
recording shows progress and a Cancel control. The filename and Blob MIME type
|
|
1666
1846
|
match the encoded format; an explicitly requested unsupported format rejects
|
|
1667
1847
|
instead of silently changing formats. With `loop: true`, a cinematic export
|
|
@@ -1671,11 +1851,32 @@ Recording runs in real time at the canvas's current pixel resolution, includes
|
|
|
1671
1851
|
post-processing, and hides editor gizmos. It pauses cinematic playback, blocks
|
|
1672
1852
|
viewer gestures, then restores the previous camera pose and controls on success,
|
|
1673
1853
|
cancellation, or failure. Keep the tab visible until recording completes.
|
|
1674
|
-
`preserveDrawingBuffer` is unnecessary for video. No audio, HTML overlays,
|
|
1675
|
-
|
|
1676
|
-
`canvas.captureStream
|
|
1854
|
+
`preserveDrawingBuffer` is unnecessary for video. No audio, HTML overlays, or
|
|
1855
|
+
format transcoding is included. The browser must support
|
|
1856
|
+
`canvas.captureStream`, and `MediaRecorder` for WebM/MP4; exports reject before framing,
|
|
1677
1857
|
during XR, during another recording, or for durations outside (0, 300] seconds.
|
|
1678
1858
|
|
|
1859
|
+
**Animated GIF.** Pass `format: "gif"` (or pick **GIF** in the menu's format
|
|
1860
|
+
select) for a GIF that loops forever, for places that don't play video, such as
|
|
1861
|
+
email, chat, and some marketplaces:
|
|
1862
|
+
|
|
1863
|
+
```ts
|
|
1864
|
+
const gif = await connection.captureVideo({
|
|
1865
|
+
mode: "turntable",
|
|
1866
|
+
format: "gif",
|
|
1867
|
+
gifWidth: 480, // pixels; the height follows the canvas. Default 480
|
|
1868
|
+
// fps defaults to 15, and a turntable to an 8-second turn
|
|
1869
|
+
});
|
|
1870
|
+
```
|
|
1871
|
+
|
|
1872
|
+
A GIF holds 256 colours per frame and stores every frame whole, so it is much
|
|
1873
|
+
larger than a video of the same length and shows some banding on smooth
|
|
1874
|
+
gradients. Frames are taken from the same stream as a video and encoded as
|
|
1875
|
+
they're captured, so a long recording doesn't build up memory. Each frame lasts
|
|
1876
|
+
as long as it really showed, so a busy device drops frames rather than playing
|
|
1877
|
+
the GIF fast. `videoBitsPerSecond` doesn't apply to GIFs. The encoder
|
|
1878
|
+
([gifenc](https://github.com/mattdesl/gifenc)) loads only when a GIF is made.
|
|
1879
|
+
|
|
1679
1880
|
### `onTextureUpload`
|
|
1680
1881
|
|
|
1681
1882
|
Optional async callback for handling texture file uploads. When provided, the viewer calls it instead of creating a blob URL, letting consumers upload to their own storage (S3, Cloudinary, a CDN, etc.) and return a durable URL that survives page reloads.
|
|
@@ -2466,13 +2667,76 @@ Enables automatic orbiting and controls its speed. Defaults are `false` and
|
|
|
2466
2667
|
<ModelViewer {...props} autoRotate autoRotateSpeed={0.75} />
|
|
2467
2668
|
```
|
|
2468
2669
|
|
|
2670
|
+
Auto-rotate doesn't start for visitors who ask their device to reduce motion.
|
|
2671
|
+
See [`reducedMotion`](#reducedmotion).
|
|
2672
|
+
|
|
2469
2673
|
### `enableKeyboardNavigation`
|
|
2470
2674
|
|
|
2471
2675
|
Optional boolean enabling camera shortcuts after the viewer canvas is clicked
|
|
2472
2676
|
or focused. Default: `false`. Use `W`/`S` to move forward/back, `A`/`D` to
|
|
2473
2677
|
truck left/right, Space/`C` to move up/down, and the arrow keys to orbit.
|
|
2474
2678
|
`Shift` + Up/Down moves forward/back. Keyboard navigation is inactive while
|
|
2475
|
-
object editing or camera controls are disabled.
|
|
2679
|
+
object editing or camera controls are disabled. While the canvas has focus, a
|
|
2680
|
+
screen reader reads out what its keys do (the `cameraKeyboardHelp` label).
|
|
2681
|
+
|
|
2682
|
+
### Accessibility
|
|
2683
|
+
|
|
2684
|
+
The viewer can be used without a mouse, and reads out what is happening:
|
|
2685
|
+
|
|
2686
|
+
- **Moving through objects with the keyboard.** Tabbing into the viewer reaches
|
|
2687
|
+
a small object list at the top of the canvas, shown only while it has focus.
|
|
2688
|
+
The arrow keys (or `Home` and `End`) move through the objects and highlight
|
|
2689
|
+
each one in the scene. Typing a letter jumps to an object whose name starts
|
|
2690
|
+
with it. `Enter` or Space selects the object, exactly as clicking it would,
|
|
2691
|
+
and `Escape` clears the selection. Screen readers hear each object's name and
|
|
2692
|
+
its position ("Wheel, 3 of 12"). The list follows the scene objects panel's
|
|
2693
|
+
order. It skips hidden objects, and objects an isolation has set aside.
|
|
2694
|
+
- **Announcing the selection.** Every change of selection is announced, whether
|
|
2695
|
+
it came from a click, the keyboard, the objects panel, or your app ("Wheel
|
|
2696
|
+
selected", "Selection cleared").
|
|
2697
|
+
- **The scene objects panel.** Each row is a button, so the panel can be used
|
|
2698
|
+
from the keyboard too. Focusing a row highlights its object, and the
|
|
2699
|
+
visibility buttons name the object they hide or show.
|
|
2700
|
+
- **Reduced motion.** See [`reducedMotion`](#reducedmotion) below.
|
|
2701
|
+
|
|
2702
|
+
All of this text can be translated with `labels`.
|
|
2703
|
+
|
|
2704
|
+
### `showObjectNavigator`
|
|
2705
|
+
|
|
2706
|
+
Optional boolean for the keyboard object list described above. Default: `true`.
|
|
2707
|
+
It takes no space until a keyboard user reaches it, so it's on by default. Set it
|
|
2708
|
+
to `false` when your app provides its own way to choose objects from the
|
|
2709
|
+
keyboard. It is hidden while measuring, since clicks then place points instead
|
|
2710
|
+
of selecting. Keyboard selections reach `onEngagement` as `part-viewed` events
|
|
2711
|
+
with `source: "keyboard"`.
|
|
2712
|
+
|
|
2713
|
+
```tsx
|
|
2714
|
+
<ModelViewer {...props} ui={{ objectNavigator: false }} />
|
|
2715
|
+
```
|
|
2716
|
+
|
|
2717
|
+
### `reducedMotion`
|
|
2718
|
+
|
|
2719
|
+
Cuts motion the viewer starts on its own, for visitors who find it hard to
|
|
2720
|
+
watch. Default: `"user"`, which follows the visitor's device setting
|
|
2721
|
+
(`prefers-reduced-motion`). `"always"` and `"never"` override it. Also
|
|
2722
|
+
`controls.reducedMotion`; the type is exported as `ReducedMotionSetting`.
|
|
2723
|
+
|
|
2724
|
+
When motion is reduced:
|
|
2725
|
+
|
|
2726
|
+
- Auto-rotate doesn't start.
|
|
2727
|
+
- A `cinematic` path with `autoPlay` waits for its play button.
|
|
2728
|
+
- Camera moves jump straight to where they're going instead of gliding: the
|
|
2729
|
+
first framing, focusing a selected object, resetting the view, saved views,
|
|
2730
|
+
guided-tour stops, and moves made through `useViewerCamera`.
|
|
2731
|
+
- Panels, menus and popups appear in place instead of sliding or fading in.
|
|
2732
|
+
Loading spinners keep turning, since they show that something is still
|
|
2733
|
+
happening.
|
|
2734
|
+
|
|
2735
|
+
Motion the visitor asks for still plays: the cinematic play button, video
|
|
2736
|
+
export, and dragging the camera. The viewer doesn't change animations your app
|
|
2737
|
+
starts, such as model clips, effects, or style rule pulses. To respect the
|
|
2738
|
+
setting there too, check `matchMedia("(prefers-reduced-motion: reduce)")` in
|
|
2739
|
+
your app. `SimpleModelViewer` takes the same prop.
|
|
2476
2740
|
|
|
2477
2741
|
### `showViewGizmo`
|
|
2478
2742
|
|
|
@@ -2495,6 +2759,8 @@ Current callback shape:
|
|
|
2495
2759
|
|
|
2496
2760
|
Optional boolean controlling whether the camera re-frames the model to fit whenever the canvas is resized, a browser window resize, a device rotation, or a side panel opening/closing (which changes how much width the canvas has). Set to `false` to preserve the user's current orbit/zoom across resizes; the camera aspect stays correct either way, so the model never distorts, it just isn't re-centered. This only affects re-fits _after_ the initial mount-time auto-fit.
|
|
2497
2761
|
|
|
2762
|
+
While a selected object is framed (see `zoomOnSelected`), a resize frames that object again rather than the whole model, so the object panel opening doesn't undo the zoom. An object with an authored `cameraState` keeps that view. Once the selection is cleared, or something else moves the camera away from the object, resizes frame the whole model again.
|
|
2763
|
+
|
|
2498
2764
|
Default:
|
|
2499
2765
|
|
|
2500
2766
|
```ts
|
|
@@ -2560,6 +2826,89 @@ Default:
|
|
|
2560
2826
|
false;
|
|
2561
2827
|
```
|
|
2562
2828
|
|
|
2829
|
+
### `webgpu`
|
|
2830
|
+
|
|
2831
|
+
Optional boolean. Draws with three.js's WebGPU renderer when the browser has
|
|
2832
|
+
WebGPU, and with WebGL otherwise. Default: `false`. Also `perf.webgpu`.
|
|
2833
|
+
|
|
2834
|
+
```tsx
|
|
2835
|
+
<ModelViewer {...props} perf={{ webgpu: true }} />
|
|
2836
|
+
```
|
|
2837
|
+
|
|
2838
|
+
The viewer asks the browser for a WebGPU adapter once per page before
|
|
2839
|
+
creating its canvas. Without one, it uses WebGL and says so once in the
|
|
2840
|
+
console. `onViewerReady` reports which renderer is drawing as
|
|
2841
|
+
`rendererBackend` (`"webgl"` or `"webgpu"`). The WebGPU renderer is loaded only
|
|
2842
|
+
by viewers that use it.
|
|
2843
|
+
|
|
2844
|
+
With WebGPU, the model, materials, bindings, hover highlight, picking,
|
|
2845
|
+
animations, decals, annotations, measurement, video and GIF export, and tone
|
|
2846
|
+
mapping and exposure work as they do with WebGL. Some features are built on
|
|
2847
|
+
WebGL and are left out:
|
|
2848
|
+
|
|
2849
|
+
- The post-processing effects (outline, bloom, depth of field, and the rest)
|
|
2850
|
+
don't draw. Tone mapping and exposure from `sceneConfig.postProcessing` still
|
|
2851
|
+
apply, through the renderer.
|
|
2852
|
+
- Contact shadows (`sceneConfig.groundShadows` in shadow-catcher mode) don't
|
|
2853
|
+
draw.
|
|
2854
|
+
- The section cut and the move gizmo (`moveModeEnabled`) aren't available, and
|
|
2855
|
+
the console says so if they're asked for.
|
|
2856
|
+
- AR and VR sessions in the browser (WebXR) aren't offered. Quick Look on iOS
|
|
2857
|
+
and the phone hand-off still work.
|
|
2858
|
+
- Measurement lines are one pixel wide.
|
|
2859
|
+
|
|
2860
|
+
`captureImage` and the Download PNG action work with WebGPU without
|
|
2861
|
+
`preserveDrawingBuffer`. A lost WebGPU device shows the same **Reload viewer**
|
|
2862
|
+
message as a lost WebGL context.
|
|
2863
|
+
|
|
2864
|
+
### Lazy loading and posters
|
|
2865
|
+
|
|
2866
|
+
`loading`, `poster` and `reveal` decide when a viewer creates its 3D view, and
|
|
2867
|
+
what shows in its place until then. They matter most on pages with many
|
|
2868
|
+
viewers, such as a product grid: browsers allow a page about 16 WebGL contexts
|
|
2869
|
+
and take the oldest away past that.
|
|
2870
|
+
|
|
2871
|
+
```tsx
|
|
2872
|
+
<ModelViewer {...props} poster="/chair.webp" />
|
|
2873
|
+
<ModelViewer {...props} poster="/chair.webp" reveal="interaction" />
|
|
2874
|
+
```
|
|
2875
|
+
|
|
2876
|
+
`loading` is `"lazy"` by default. A lazy viewer:
|
|
2877
|
+
|
|
2878
|
+
- starts its 3D view when it comes within about 200px of the screen. One that's
|
|
2879
|
+
on the screen when the page loads starts at once.
|
|
2880
|
+
- shares the page's live 3D views with the other lazy viewers: at most 12, or 6
|
|
2881
|
+
on phones and tablets. Viewers on the screen come first. One scrolled away
|
|
2882
|
+
from keeps its view while there's room, so coming back is instant. When the
|
|
2883
|
+
room is needed, it's put away, leaving a picture of its last frame, and is
|
|
2884
|
+
built again when it comes back near the screen.
|
|
2885
|
+
- comes back as it was: the camera, picked variants, light and section-cut
|
|
2886
|
+
changes, isolation, the exploded view, the camera controller, the UV checker,
|
|
2887
|
+
the annotation filter, and, when the viewer keeps `objectBindings` itself,
|
|
2888
|
+
the changes made to them. The selection and open panels don't come back. `onModelLoaded` and
|
|
2889
|
+
`onViewerReady` run again, with a new viewer.
|
|
2890
|
+
- stops drawing while it's off the screen, and starts again when it's back.
|
|
2891
|
+
Browsers already stop drawing in hidden tabs.
|
|
2892
|
+
|
|
2893
|
+
`loading="eager"` starts the 3D view at once and keeps it. Eager viewers don't
|
|
2894
|
+
count towards the 12. `BindingBuilder`'s preview is eager.
|
|
2895
|
+
|
|
2896
|
+
`poster` is an image shown in the viewer's place until its 3D view is drawn,
|
|
2897
|
+
then fades out; the loading message shows over it. It's fitted inside the
|
|
2898
|
+
viewer. To fill the viewer instead, set the CSS variable
|
|
2899
|
+
`--ri-poster-fit: cover` on a parent. A render of the model framed as the
|
|
2900
|
+
viewer frames it works best: [`captureImage`](#onviewerready) can make one.
|
|
2901
|
+
|
|
2902
|
+
`reveal="interaction"` keeps the poster, with a **View in 3D** button, until the
|
|
2903
|
+
visitor presses it, so only the products someone picks use a 3D view. The
|
|
2904
|
+
button's text is the [`labels`](https://react-immersive.liveroom.dev/docs/reference/labels)
|
|
2905
|
+
key `viewIn3D`. `reveal` is `"auto"` by default: the view starts on its own.
|
|
2906
|
+
|
|
2907
|
+
The same three props work on [`SimpleModelViewer`](#simplemodelviewer-public-api)
|
|
2908
|
+
and [`CompareViewer`](#compareviewer).
|
|
2909
|
+
|
|
2910
|
+
---
|
|
2911
|
+
|
|
2563
2912
|
### `performanceProfile`
|
|
2564
2913
|
|
|
2565
2914
|
Optional control over how aggressively the viewer trades visual fidelity for a stable WebGL context on constrained GPUs.
|
|
@@ -2584,9 +2933,11 @@ If the WebGL context is lost, rendering pauses and the viewer displays a
|
|
|
2584
2933
|
the viewer automatically rebuilds its canvas and scene; the reload button starts
|
|
2585
2934
|
the same rebuild without waiting for browser restoration.
|
|
2586
2935
|
|
|
2587
|
-
Recovery uses the current props and cached model assets, cancels video
|
|
2588
|
-
|
|
2589
|
-
|
|
2936
|
+
Recovery uses the current props and cached model assets, and cancels video
|
|
2937
|
+
recording. The rebuilt viewer keeps its camera and the visitor's changes, as a
|
|
2938
|
+
lazy viewer does when it comes back (see
|
|
2939
|
+
[Lazy loading and posters](#lazy-loading-and-posters)); the selection,
|
|
2940
|
+
measurements and playback start over.
|
|
2590
2941
|
`onSceneLoaded`, `onModelLoaded`, `onAnimationsReady`, and `onViewerReady` run again
|
|
2591
2942
|
for the rebuilt scene, so integrations receive fresh controls and object refs.
|
|
2592
2943
|
|
|
@@ -2713,43 +3064,79 @@ false;
|
|
|
2713
3064
|
|
|
2714
3065
|
### `showMaterialVariants`
|
|
2715
3066
|
|
|
2716
|
-
Shows
|
|
3067
|
+
Shows swatch buttons, centered at the bottom of the viewer, for switching
|
|
2717
3068
|
between the model's glTF material variants (the `KHR_materials_variants`
|
|
2718
|
-
extension): the colorways, finishes, or trims a product file ships with.
|
|
2719
|
-
|
|
3069
|
+
extension): the colorways, finishes, or trims a product file ships with. Each
|
|
3070
|
+
button shows the variant's colour when its material is a plain colour. It
|
|
3071
|
+
appears for a glTF/GLB with any variants. Default `true`.
|
|
3072
|
+
|
|
3073
|
+
Variants come in **option groups**, one row each. Choosing a variant replaces
|
|
3074
|
+
the other choice in its row and keeps the other rows, so a paint colour and a
|
|
3075
|
+
wheel style combine. Clicking the chosen variant again returns that row to the
|
|
3076
|
+
file's default materials. The viewer works out the groups from the file:
|
|
2720
3077
|
|
|
2721
|
-
|
|
2722
|
-
|
|
3078
|
+
- A name like `"Paint: Red"` or `"Paint / Red"` puts the variant in a row
|
|
3079
|
+
labelled **Paint**, with a button labelled **Red**.
|
|
3080
|
+
- Otherwise, variants that restyle the same parts share a row, and variants
|
|
3081
|
+
that restyle different parts get separate rows. A shoe's `"midnight"`,
|
|
3082
|
+
`"beach"` and `"street"` colorways, which all restyle the same parts, are one
|
|
3083
|
+
row.
|
|
3084
|
+
|
|
3085
|
+
The variants shown are `sceneConfig.model.activeVariants`, at most one from
|
|
3086
|
+
each group, which is also how you choose them yourself:
|
|
2723
3087
|
|
|
2724
3088
|
```tsx
|
|
3089
|
+
import { chooseMaterialVariant, useViewer } from "@liveroom-tech/react-immersive";
|
|
3090
|
+
|
|
2725
3091
|
const viewer = useViewer();
|
|
2726
3092
|
|
|
2727
|
-
<ModelViewer modelUrl="/
|
|
3093
|
+
<ModelViewer modelUrl="/car.glb" licenseKey="your-license-key" {...viewer.props} />;
|
|
3094
|
+
|
|
3095
|
+
// Once the model has loaded
|
|
3096
|
+
viewer.model.materialVariants; // ["Paint: Red", "Paint: Blue", "Wheels: Sport", "Wheels: Classic"]
|
|
3097
|
+
viewer.model.materialVariantGroups;
|
|
3098
|
+
// [
|
|
3099
|
+
// { name: "Paint", variants: [{ name: "Paint: Red", label: "Red", swatch: "#ff0000" }, ...] },
|
|
3100
|
+
// { name: "Wheels", variants: [{ name: "Wheels: Sport", label: "Sport" }, ...] },
|
|
3101
|
+
// ]
|
|
2728
3102
|
|
|
2729
|
-
|
|
2730
|
-
viewer.model
|
|
3103
|
+
viewer.scene.updateSceneConfig({ model: { activeVariants: ["Paint: Red", "Wheels: Sport"] } });
|
|
3104
|
+
viewer.scene.updateSceneConfig({ model: { activeVariants: [] } }); // the defaults
|
|
2731
3105
|
|
|
2732
|
-
|
|
2733
|
-
viewer.scene.
|
|
3106
|
+
// Your own swatches: swap a choice within its group, keeping the others.
|
|
3107
|
+
const active = viewer.scene.sceneConfig.model.activeVariants ?? [];
|
|
3108
|
+
viewer.scene.updateSceneConfig({
|
|
3109
|
+
model: {
|
|
3110
|
+
activeVariants: chooseMaterialVariant(active, "Paint: Blue", viewer.model.materialVariantGroups),
|
|
3111
|
+
},
|
|
3112
|
+
});
|
|
2734
3113
|
```
|
|
2735
3114
|
|
|
2736
3115
|
- A variant sets each object's starting material. `objectBindings` material
|
|
2737
3116
|
overrides (`style.material`) still apply on top of it, and clearing an
|
|
2738
3117
|
override returns to the variant's material rather than the file's default.
|
|
2739
|
-
- Objects
|
|
3118
|
+
- Objects no active variant maps keep their default material, bound or not.
|
|
3119
|
+
If two active variants map the same object, the later one in the list wins.
|
|
2740
3120
|
- A variant's materials and textures load the first time it's chosen; the
|
|
2741
3121
|
current look stays until they're ready.
|
|
2742
3122
|
- With `onSceneConfigChange`, a pick goes to your scene config (`useViewer`
|
|
2743
|
-
wires this up). Without it, `ModelViewer` keeps the
|
|
2744
|
-
`sceneConfig.model.
|
|
2745
|
-
- A name the model doesn't have
|
|
2746
|
-
|
|
3123
|
+
wires this up). Without it, `ModelViewer` keeps the picks itself until
|
|
3124
|
+
`sceneConfig.model.activeVariants` or `modelUrl` changes.
|
|
3125
|
+
- A name the model doesn't have is skipped with a console warning listing the
|
|
3126
|
+
model's variants.
|
|
2747
3127
|
- An action can switch variants with a scene-config effect:
|
|
2748
|
-
`{ sceneConfig: { model: {
|
|
2749
|
-
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
3128
|
+
`{ sceneConfig: { model: { activeVariants: ["Paint: Red"] } } }`.
|
|
3129
|
+
- Swatches use the colour of the material a variant puts on the most vertices.
|
|
3130
|
+
A textured material gets no swatch, just its label, so showing the buttons
|
|
3131
|
+
never loads textures.
|
|
3132
|
+
- Each pick reaches [`onEngagement`](#onengagement--debugengagement) as a
|
|
3133
|
+
`variant-chosen` event with its `group`.
|
|
3134
|
+
- `onModelLoaded` reports `materialVariants` and `materialVariantGroups`, and
|
|
3135
|
+
BindingBuilder's Scene tab (**General**, one select per group) picks the
|
|
3136
|
+
variants the exported scene config starts with. OBJ, FBX, USDZ, STL, PLY, and
|
|
3137
|
+
streamed 3D Tiles models have no variants.
|
|
3138
|
+
- Scene configs saved before option groups, with a single
|
|
3139
|
+
`model.materialVariant`, still show that variant.
|
|
2753
3140
|
|
|
2754
3141
|
### Decals
|
|
2755
3142
|
|
|
@@ -2899,7 +3286,8 @@ showIsolateControls?: boolean; // tools.isolate
|
|
|
2899
3286
|
|
|
2900
3287
|
Shows a section-cut button over the canvas (`tools.section`). It cuts the model
|
|
2901
3288
|
open along an axis, hiding everything on one side of a plane, to look inside
|
|
2902
|
-
assemblies, equipment, and buildings. Default `false`.
|
|
3289
|
+
assemblies, equipment, and buildings. Default `false`. Not available with
|
|
3290
|
+
[`webgpu`](#webgpu).
|
|
2903
3291
|
|
|
2904
3292
|
With the cut on, the toolbar picks the axis (X, Y, or Z), the position along
|
|
2905
3293
|
it, and which side stays, and a plane appears across the model that you can
|
|
@@ -2951,7 +3339,7 @@ type CinematicConfig = {
|
|
|
2951
3339
|
waypoints?: CinematicWaypoint[]; // camera keyframes to glide through
|
|
2952
3340
|
duration?: number; // seconds for one full pass (auto-scales when omitted)
|
|
2953
3341
|
loop?: boolean; // default true
|
|
2954
|
-
autoPlay?: boolean; // start once the model has loaded and framed (default false)
|
|
3342
|
+
autoPlay?: boolean; // start once the model has loaded and framed (default false); waits for the play button under reduced motion
|
|
2955
3343
|
};
|
|
2956
3344
|
|
|
2957
3345
|
type CinematicWaypoint = {
|
|
@@ -3099,7 +3487,10 @@ See the full variable list in the [`ModelViewer` docs](https://react-immersive.l
|
|
|
3099
3487
|
```ts
|
|
3100
3488
|
type SimpleModelViewerProps = {
|
|
3101
3489
|
modelUrl: string;
|
|
3102
|
-
|
|
3490
|
+
poster?: string; // see ModelViewer's poster
|
|
3491
|
+
loading?: "lazy" | "eager"; // default "lazy"
|
|
3492
|
+
reveal?: "auto" | "interaction"; // default "auto"
|
|
3493
|
+
modelFormat?: "glb" | "gltf" | "obj" | "fbx" | "usdz" | "stl" | "ply";
|
|
3103
3494
|
dracoDecoderPath?: string | false;
|
|
3104
3495
|
ktx2TranscoderPath?: string | false;
|
|
3105
3496
|
meshopt?: boolean;
|
|
@@ -3111,6 +3502,7 @@ type SimpleModelViewerProps = {
|
|
|
3111
3502
|
highlightOnHover?: boolean;
|
|
3112
3503
|
showLoadingOverlay?: boolean;
|
|
3113
3504
|
refitOnResize?: boolean;
|
|
3505
|
+
reducedMotion?: "user" | "always" | "never"; // see ModelViewer's reducedMotion
|
|
3114
3506
|
// Initial values for the scene settings (environment) panel
|
|
3115
3507
|
backgroundEnabled?: boolean;
|
|
3116
3508
|
autoRotate?: boolean;
|
|
@@ -3129,13 +3521,21 @@ type SimpleModelViewerProps = {
|
|
|
3129
3521
|
|
|
3130
3522
|
### `modelUrl`
|
|
3131
3523
|
|
|
3132
|
-
URL or public path to the `.glb`, `.gltf`, `.obj`, `.fbx`, or `.
|
|
3524
|
+
URL or public path to the `.glb`, `.gltf`, `.obj`, `.fbx`, `.usdz`, `.stl`, or `.ply` model to inspect. Hosted assets must serve external dependencies at their declared relative paths; USDZ dependencies are packaged internally. Use `modelFormat` when a signed or extensionless URL cannot be auto-detected.
|
|
3133
3525
|
|
|
3134
3526
|
### `modelFormat`
|
|
3135
3527
|
|
|
3136
3528
|
Optional explicit format for signed or extensionless model URLs. Normal URLs
|
|
3137
3529
|
ending in a supported extension are detected automatically.
|
|
3138
3530
|
|
|
3531
|
+
### `poster` / `loading` / `reveal`
|
|
3532
|
+
|
|
3533
|
+
Work as on [`ModelViewer`](#lazy-loading-and-posters), sharing the same
|
|
3534
|
+
page-wide count of live 3D views. When a `SimpleModelViewer` is put away, only
|
|
3535
|
+
its canvas goes: the panels and their settings stay, and the canvas comes back
|
|
3536
|
+
with its camera, hidden objects and selection.
|
|
3537
|
+
`onModelLoaded` runs again when it does.
|
|
3538
|
+
|
|
3139
3539
|
### `backgroundColor`
|
|
3140
3540
|
|
|
3141
3541
|
Optional canvas background color.
|
|
@@ -3237,7 +3637,7 @@ true;
|
|
|
3237
3637
|
|
|
3238
3638
|
### `enableModelUpload`
|
|
3239
3639
|
|
|
3240
|
-
Optional boolean that swaps the fixed `modelUrl` workflow for a built-in upload UI. It accepts `.glb`, standalone/data-URI `.gltf`, `.obj`, `.fbx`, `.usdz`, or a `.zip` bundle. ZIP bundles may contain a GLTF with its buffers/textures, an OBJ with its optional MTL/textures, or an FBX with external textures. A USDZ file is already a self-contained package and is uploaded directly.
|
|
3640
|
+
Optional boolean that swaps the fixed `modelUrl` workflow for a built-in upload UI. It accepts `.glb`, standalone/data-URI `.gltf`, `.obj`, `.fbx`, `.usdz`, `.stl`, `.ply`, or a `.zip` bundle. ZIP bundles may contain a GLTF with its buffers/textures, an OBJ with its optional MTL/textures, or an FBX with external textures. A USDZ file is already a self-contained package and is uploaded directly.
|
|
3241
3641
|
|
|
3242
3642
|
Default:
|
|
3243
3643
|
|
|
@@ -3424,8 +3824,8 @@ To use this library successfully, your model asset should follow these expectati
|
|
|
3424
3824
|
- the mesh node names must match the keys in `objectBindings` (or be referenced via `modelObjectId`)
|
|
3425
3825
|
- target nodes should have geometry and a supported Three.js mesh material; imported materials are normalized into the editable PBR pipeline
|
|
3426
3826
|
- the file should be accessible from the browser at `modelUrl`; hosted GLTF, OBJ, and FBX assets must also serve external dependencies at their declared relative paths
|
|
3427
|
-
- OBJ
|
|
3428
|
-
- OBJ
|
|
3827
|
+
- OBJ, STL, and PLY do not contain animations; supported FBX and USDZ animation clips are exposed through the same animation controls as glTF
|
|
3828
|
+
- OBJ, STL, and PLY have no standard unit metadata, so set an appropriate measurement label/scene scale when real-world dimensions matter
|
|
3429
3829
|
|
|
3430
3830
|
If a node name or material name does not match, that object will not render through the interactive binding flow.
|
|
3431
3831
|
|
|
@@ -3465,3 +3865,32 @@ Two related picking notes that apply to models of every size:
|
|
|
3465
3865
|
## License
|
|
3466
3866
|
|
|
3467
3867
|
Proprietary, see [LICENSE](./LICENSE). Installing the package does not itself grant a right to use it in production; a valid `licenseKey` from the [Developer Portal](https://react-immersive.liveroom.dev) is required, and usage is bound to the terms of your license tier.
|
|
3868
|
+
|
|
3869
|
+
### Rich annotations
|
|
3870
|
+
|
|
3871
|
+
`sceneConfig.annotations` supports optional `cameraView` (position, target,
|
|
3872
|
+
orbit angles, fov and zoom), `images: [{ src, alt }]`, `links: [{ href, label }]`,
|
|
3873
|
+
`category`, and `minDistance` / `maxDistance` in scene units. BindingBuilder's
|
|
3874
|
+
Annotations tab authors these fields; new markers use automatic close-up tour
|
|
3875
|
+
framing. Explicitly capture a camera view for custom framing. Marker selection
|
|
3876
|
+
and guided tours restore saved views when provided. The viewer's category chips
|
|
3877
|
+
filter markers and tour steps.
|
|
3878
|
+
Use `annotationCategories` (`null` = all, `[]` = none) and
|
|
3879
|
+
`onAnnotationCategoriesChange` for controlled filtering, and
|
|
3880
|
+
`ui.annotationFilters: false` to hide the built-in chips. Distance limits affect
|
|
3881
|
+
markers only, so their content remains available through tours. Images stay at
|
|
3882
|
+
their HTTP(S) or relative URLs and are not bundled into exports automatically.
|
|
3883
|
+
|
|
3884
|
+
## Translatable viewer text
|
|
3885
|
+
|
|
3886
|
+
Both viewers accept a partial `labels` dictionary. Omitted keys use English; changing labels updates text without reloading the model. Named placeholders such as `{count}` can be reordered in translations.
|
|
3887
|
+
|
|
3888
|
+
```tsx
|
|
3889
|
+
<ModelViewer
|
|
3890
|
+
licenseKey="your-license-key"
|
|
3891
|
+
modelUrl="/car.glb"
|
|
3892
|
+
labels={{ guidedTour: "Visite guidée", tapToPlace: "Touchez pour placer", visibleEntries: "{count} éléments visibles" }}
|
|
3893
|
+
/>
|
|
3894
|
+
```
|
|
3895
|
+
|
|
3896
|
+
`ModelViewerLabels`, `ModelViewerLabelKey`, and `DEFAULT_MODEL_VIEWER_LABELS` are exported from the main package, `/simple-model-viewer`, and `/utils`. See the [full catalog](https://react-immersive.liveroom.dev/docs/reference/labels). Annotation content, metadata, and custom panels use your application's translations.
|