@liveroom-tech/react-immersive 4.3.1 → 5.0.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 +697 -83
- package/dist/ModelViewer-CbbJT85n.d.mts +448 -0
- package/dist/ModelViewer-DOexHkJC.d.ts +448 -0
- package/dist/binding-builder.css +1 -0
- package/dist/binding-builder.d.mts +6 -0
- package/dist/binding-builder.d.ts +6 -0
- package/dist/binding-builder.js +3694 -0
- package/dist/binding-builder.mjs +1 -0
- package/dist/chunk-7EW6JZ46.mjs +27 -0
- package/dist/chunk-BGYK4Y2Y.mjs +1 -0
- package/dist/chunk-MMO7KCYU.mjs +3 -0
- package/dist/chunk-NVY7N7CU.mjs +3 -0
- package/dist/chunk-SEESCGUS.mjs +1 -0
- package/dist/chunk-WFEYRUZC.mjs +1 -0
- package/dist/chunk-XTLSAYAQ.mjs +1 -0
- package/dist/hosted.css +1 -1
- package/dist/hosted.d.mts +29 -9
- package/dist/hosted.d.ts +29 -9
- package/dist/hosted.js +11 -8
- package/dist/hosted.mjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.mts +107 -83
- package/dist/index.d.ts +107 -83
- package/dist/index.js +11 -8
- package/dist/index.mjs +1 -1
- package/dist/modelInfo-CEmmvVCH.d.mts +17 -0
- package/dist/modelInfo-CEmmvVCH.d.ts +17 -0
- package/dist/{objectBinding-CBRYMYk6.d.ts → objectCatalog-5W8n5xNQ.d.mts} +55 -3
- package/dist/{objectBinding-CBRYMYk6.d.mts → objectCatalog-5W8n5xNQ.d.ts} +55 -3
- package/dist/publicProps-tJuWZgT3.d.mts +12 -0
- package/dist/publicProps-tJuWZgT3.d.ts +12 -0
- package/dist/simple-model-viewer.css +1 -0
- package/dist/simple-model-viewer.d.mts +38 -0
- package/dist/simple-model-viewer.d.ts +38 -0
- package/dist/simple-model-viewer.js +3666 -0
- package/dist/simple-model-viewer.mjs +1 -0
- package/dist/utils.d.mts +49 -3
- package/dist/utils.d.ts +49 -3
- package/dist/utils.js +1 -1
- package/dist/utils.mjs +1 -1
- package/package.json +28 -18
- package/dist/chunk-7X37KAPE.mjs +0 -1
- package/dist/chunk-IUALYWYG.mjs +0 -26
- package/dist/chunk-K7DMBJBJ.mjs +0 -1
- package/dist/publicProps-BST1E8an.d.mts +0 -349
- package/dist/publicProps-Bv3_PCP0.d.ts +0 -349
package/README.md
CHANGED
|
@@ -10,16 +10,15 @@ The library currently exports:
|
|
|
10
10
|
|
|
11
11
|
```ts
|
|
12
12
|
import {
|
|
13
|
-
BindingBuilder,
|
|
14
13
|
material,
|
|
15
14
|
ModelViewer,
|
|
16
15
|
patchBindings,
|
|
17
16
|
patchSceneConfig,
|
|
18
|
-
SimpleModelViewer,
|
|
19
17
|
useObjectBinding,
|
|
20
18
|
useObjectBindingIds,
|
|
21
19
|
useObjectBindings,
|
|
22
20
|
useSceneConfig,
|
|
21
|
+
useViewer,
|
|
23
22
|
useViewerActions,
|
|
24
23
|
useViewerAnimations,
|
|
25
24
|
useViewerCamera,
|
|
@@ -31,8 +30,13 @@ import {
|
|
|
31
30
|
MATERIAL_BLENDING_MODES,
|
|
32
31
|
MATERIAL_SIDES,
|
|
33
32
|
} from "@liveroom-tech/react-immersive";
|
|
33
|
+
import { SimpleModelViewer } from "@liveroom-tech/react-immersive/simple-model-viewer";
|
|
34
|
+
import { BindingBuilder } from "@liveroom-tech/react-immersive/binding-builder";
|
|
34
35
|
```
|
|
35
36
|
|
|
37
|
+
Each component has its own entry point, so an app downloads only the ones it
|
|
38
|
+
imports.
|
|
39
|
+
|
|
36
40
|
It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindingsController`, `ObjectActionEvent`, `ObjectTransformSpace`, `SceneConfig`, `SceneConfigPatch`, `SceneConfigController`, `AnimationControls`, `CinematicConfig` / `CinematicWaypoint`, and related shapes used throughout this document.
|
|
37
41
|
|
|
38
42
|
`ModelViewer` provides:
|
|
@@ -41,7 +45,7 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
41
45
|
- camera-driven 3D Tiles streaming through `tilesetUrl`, with configurable screen-space error and bounded tile cache
|
|
42
46
|
- orbit/pan/zoom camera controls via `CameraControls`
|
|
43
47
|
- 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
|
|
44
|
-
-
|
|
48
|
+
- scene lighting through `sceneConfig.lighting`, with the `sceneLight` helper and a runtime lights controller
|
|
45
49
|
- optional custom camera configuration through a `camera` prop
|
|
46
50
|
- optional custom background color through a `backgroundColor` prop
|
|
47
51
|
- optional shadow rendering toggle through a `shadows` prop (on by default)
|
|
@@ -51,6 +55,7 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
51
55
|
- optional per-object move and rotate gizmo through `moveModeEnabled`, with World/Local axis alignment through `objectTransformSpace`
|
|
52
56
|
- optional custom left object data panel through `customObjectBindingDataPanel`
|
|
53
57
|
- optional custom right scene objects panel through `customSceneObjectsPanel`
|
|
58
|
+
- your own UI over the 3D view through `overlay`, and react-three-fiber content inside the scene through `unsafe_sceneChildren`
|
|
54
59
|
- optional hiding of either built-in panel through `showObjectBindingDataPanel` and `showSceneObjectsPanel`
|
|
55
60
|
- 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
|
|
56
61
|
- an optional UV Checker toolbar button through `showUvCheckerButton`, for inspecting UV scale, stretching, seams, orientation, and missing UV0 coordinates directly in `ModelViewer`
|
|
@@ -73,6 +78,7 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
73
78
|
- hover and selected-state highlighting
|
|
74
79
|
- external selection state control through `selectedObject` and `onObjectSelect`
|
|
75
80
|
- hover callbacks through `onObjectHover`
|
|
81
|
+
- double-click, right-click, and touch long-press callbacks through `onObjectDoubleClick` and `onObjectContextMenu`
|
|
76
82
|
- model-ready callbacks through `onModelLoaded`
|
|
77
83
|
- model-load error callbacks through `onLoadError`
|
|
78
84
|
- camera change callbacks through `onCameraChange`
|
|
@@ -98,7 +104,7 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
|
|
|
98
104
|
|
|
99
105
|
`SimpleModelViewer` provides:
|
|
100
106
|
|
|
101
|
-
- a lightweight GLB/GLTF/OBJ/FBX/USDZ viewer
|
|
107
|
+
- a lightweight GLB/GLTF/OBJ/FBX/USDZ viewer with no bindings or actions that does not require a `licenseKey`
|
|
102
108
|
- 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
|
|
103
109
|
- built-in scene objects panel with search and visibility toggles
|
|
104
110
|
- 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
|
|
@@ -165,24 +171,33 @@ has its own meaning, state, or behavior," object bindings are a strong fit.
|
|
|
165
171
|
## Installation
|
|
166
172
|
|
|
167
173
|
```bash
|
|
168
|
-
npm install @liveroom-tech/react-immersive
|
|
174
|
+
npm install @liveroom-tech/react-immersive three @react-three/fiber
|
|
169
175
|
```
|
|
170
176
|
|
|
171
|
-
Import the
|
|
172
|
-
Next.js `app/layout.tsx`):
|
|
177
|
+
Import the stylesheet for each component you use, once, from your
|
|
178
|
+
application root (for example, Next.js `app/layout.tsx`):
|
|
173
179
|
|
|
174
180
|
```tsx
|
|
175
|
-
import "@liveroom-tech/react-immersive/styles.css";
|
|
181
|
+
import "@liveroom-tech/react-immersive/styles.css"; // ModelViewer
|
|
182
|
+
import "@liveroom-tech/react-immersive/simple-model-viewer.css"; // SimpleModelViewer
|
|
183
|
+
import "@liveroom-tech/react-immersive/binding-builder.css"; // BindingBuilder
|
|
176
184
|
```
|
|
177
185
|
|
|
178
186
|
The styles ship as a static CSS file rather than a runtime-injected `<style>`
|
|
179
187
|
tag, so a strict CSP does not need `style-src 'unsafe-inline'`. Tailwind is not
|
|
180
188
|
required in the consuming app.
|
|
181
189
|
|
|
182
|
-
|
|
190
|
+
If a component's stylesheet is missing, it renders unstyled, and in
|
|
191
|
+
development it logs a console warning naming the import to add.
|
|
192
|
+
|
|
193
|
+
Peer dependencies, which your app provides so there's exactly one copy of each:
|
|
194
|
+
|
|
195
|
+
- `react` and `react-dom` 19
|
|
196
|
+
- `three` 0.184
|
|
197
|
+
- `@react-three/fiber` 9
|
|
183
198
|
|
|
184
|
-
-
|
|
185
|
-
|
|
199
|
+
Everything else, including drei, post-processing, and WebXR support, installs
|
|
200
|
+
with the package.
|
|
186
201
|
|
|
187
202
|
`ModelViewer` and `BindingBuilder` require a `licenseKey` at runtime (see [`licenseKey`](#licensekey)). Get one from the Developer Portal, see [Resources](#resources) below.
|
|
188
203
|
|
|
@@ -199,6 +214,74 @@ Peer dependencies:
|
|
|
199
214
|
|
|
200
215
|
## Quick Start
|
|
201
216
|
|
|
217
|
+
The shortest working viewer lets `ModelViewer` generate the bindings itself:
|
|
218
|
+
|
|
219
|
+
```tsx
|
|
220
|
+
import { ModelViewer } from "@liveroom-tech/react-immersive";
|
|
221
|
+
|
|
222
|
+
export default function Example() {
|
|
223
|
+
return (
|
|
224
|
+
<div style={{ height: "100vh", overflow: "hidden" }}>
|
|
225
|
+
<ModelViewer
|
|
226
|
+
modelUrl="/model.glb"
|
|
227
|
+
licenseKey="your-license-key"
|
|
228
|
+
/>
|
|
229
|
+
</div>
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Every mesh in the model can then be hovered and selected, and opens the side
|
|
235
|
+
panel with Change Color, Change Material, and Toggle Visibility (or, for meshes
|
|
236
|
+
whose names contain "light", Toggle Light, Change Light Color, and Set
|
|
237
|
+
Brightness). See
|
|
238
|
+
[Generating bindings automatically](#generating-bindings-automatically) for how
|
|
239
|
+
to keep and refine the generated bindings.
|
|
240
|
+
|
|
241
|
+
To control the viewer from your own UI, use `useViewer`. It connects every
|
|
242
|
+
viewer hook to `ModelViewer` at once:
|
|
243
|
+
|
|
244
|
+
```tsx
|
|
245
|
+
import { ModelViewer, useViewer } from "@liveroom-tech/react-immersive";
|
|
246
|
+
|
|
247
|
+
export default function Configurator() {
|
|
248
|
+
const viewer = useViewer();
|
|
249
|
+
|
|
250
|
+
return (
|
|
251
|
+
<div style={{ height: "100vh", overflow: "hidden" }}>
|
|
252
|
+
<ModelViewer
|
|
253
|
+
modelUrl="/model.glb"
|
|
254
|
+
licenseKey="your-license-key"
|
|
255
|
+
{...viewer.props}
|
|
256
|
+
/>
|
|
257
|
+
<button
|
|
258
|
+
onClick={() => viewer.bindings.setBaseColor({ group: "paint" }, "#b91c1c")}
|
|
259
|
+
>
|
|
260
|
+
Paint it red
|
|
261
|
+
</button>
|
|
262
|
+
<button onClick={() => viewer.camera.resetView()}>Reset view</button>
|
|
263
|
+
</div>
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
`viewer.bindings`, `viewer.selection`, `viewer.hover`, `viewer.camera`,
|
|
269
|
+
`viewer.model`, `viewer.animations`, `viewer.actions`, `viewer.scene`,
|
|
270
|
+
`viewer.effects`, and `viewer.connection` are the results of the hooks they're
|
|
271
|
+
named after (`useObjectBindings`, `useViewerSelection`, and so on), so
|
|
272
|
+
everything below applies to them. Without a `bindings` option, `useViewer`
|
|
273
|
+
generates bindings from the model like `objectBindings="auto"`; pass
|
|
274
|
+
`useViewer({ bindings })` to start from your own. It also accepts
|
|
275
|
+
`sceneConfig`, `camera` (the `useViewerCamera` options), and `onAction`,
|
|
276
|
+
`onObjectSelect`, `onObjectHover`, and `onLoadError` callbacks. `sceneConfig`
|
|
277
|
+
is passed to `ModelViewer` only once you've supplied one or changed the scene
|
|
278
|
+
through `viewer.scene`, so the default scene behaves as if none were passed.
|
|
279
|
+
Props set on `ModelViewer` after the spread take precedence.
|
|
280
|
+
|
|
281
|
+
The full example below authors bindings by hand and wires up every hook
|
|
282
|
+
individually, which is what `useViewer` does for you. Wire them yourself when
|
|
283
|
+
state lives elsewhere, such as bindings owned by a store:
|
|
284
|
+
|
|
202
285
|
```tsx
|
|
203
286
|
import {
|
|
204
287
|
ModelViewer,
|
|
@@ -488,8 +571,7 @@ pure `patchSceneConfig(sceneConfig, patch)` utility when a store or parent owns
|
|
|
488
571
|
the scene state. The namespaced `lights` controller also provides `add`,
|
|
489
572
|
`update`, `remove`, `show`, `hide`, `toggle`, `setIntensity`, `setColor`,
|
|
490
573
|
`attach`, and `detach`. Attached lights follow a bound model object without an
|
|
491
|
-
R3F light component.
|
|
492
|
-
leave it unset when using this controller.
|
|
574
|
+
R3F light component.
|
|
493
575
|
|
|
494
576
|
For high-frequency visual effects, use `useViewerEffects`. It mutates the live
|
|
495
577
|
viewer efficiently, requests demand-rendered frames automatically, and restores
|
|
@@ -579,7 +661,9 @@ const {
|
|
|
579
661
|
error,
|
|
580
662
|
bounds,
|
|
581
663
|
objectCount,
|
|
664
|
+
objects,
|
|
582
665
|
handleModelLoaded,
|
|
666
|
+
handleObjectCatalog,
|
|
583
667
|
handleLoadError,
|
|
584
668
|
} = useViewerModel();
|
|
585
669
|
|
|
@@ -588,6 +672,7 @@ const {
|
|
|
588
672
|
licenseKey="your-license-key"
|
|
589
673
|
objectBindings={objectBindings}
|
|
590
674
|
onModelLoaded={handleModelLoaded}
|
|
675
|
+
onObjectCatalog={handleObjectCatalog}
|
|
591
676
|
onLoadError={handleLoadError}
|
|
592
677
|
/>;
|
|
593
678
|
```
|
|
@@ -599,7 +684,10 @@ const {
|
|
|
599
684
|
- `error`: the most recent load/render error, or `null`
|
|
600
685
|
- `bounds`: the loaded scene bounds as `min`, `max`, `center`, and `size`
|
|
601
686
|
- `objectCount`: the number of mesh objects in the loaded scene
|
|
687
|
+
- `objects`: every bindable object in the model as a `ViewerObjectInfo`, once
|
|
688
|
+
`handleObjectCatalog` is passed to `onObjectCatalog`
|
|
602
689
|
- `handleModelLoaded`: a callback you can pass directly to `onModelLoaded`
|
|
690
|
+
- `handleObjectCatalog`: a callback you can pass directly to `onObjectCatalog`
|
|
603
691
|
- `handleLoadError`: a callback you can pass directly to `onLoadError`
|
|
604
692
|
|
|
605
693
|
If you want to inspect or trigger binding actions programmatically, the library also exports:
|
|
@@ -636,7 +724,8 @@ Actions can declare ordered `effects` for object-binding patches, scene patches,
|
|
|
636
724
|
Basic usage:
|
|
637
725
|
|
|
638
726
|
```tsx
|
|
639
|
-
import
|
|
727
|
+
import "@liveroom-tech/react-immersive/binding-builder.css";
|
|
728
|
+
import { BindingBuilder } from "@liveroom-tech/react-immersive/binding-builder";
|
|
640
729
|
|
|
641
730
|
export default function App() {
|
|
642
731
|
return <BindingBuilder licenseKey="your-license-key" />;
|
|
@@ -655,31 +744,11 @@ type BindingBuilderProps = {
|
|
|
655
744
|
};
|
|
656
745
|
```
|
|
657
746
|
|
|
658
|
-
Pass `tilesetUrl` to `ModelViewer` for a processed 3D Tiles asset. The original
|
|
659
|
-
`modelUrl` remains the durable source/fallback URL, but is not downloaded by the
|
|
660
|
-
viewer while a tileset is present:
|
|
661
|
-
|
|
662
|
-
```tsx
|
|
663
|
-
<ModelViewer
|
|
664
|
-
modelUrl="https://assets.example.com/source/building.glb"
|
|
665
|
-
tilesetUrl="https://assets.example.com/tiles/building/tileset.json"
|
|
666
|
-
tilesErrorTarget={8}
|
|
667
|
-
tilesCacheSize={800}
|
|
668
|
-
licenseKey="your-license-key"
|
|
669
|
-
objectBindings={{}}
|
|
670
|
-
/>
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
Tiles are a dynamic level-of-detail scene, so automatic mesh binding generation
|
|
674
|
-
is intentionally disabled in streamed `BindingBuilder` projects. Scene settings
|
|
675
|
-
remain editable. Per-part editing requires the conversion pipeline to provide a
|
|
676
|
-
stable CAD/object catalog that can be mapped to tile feature metadata.
|
|
677
|
-
|
|
678
747
|
`licenseKey` is validated the same way as `ModelViewer`'s (see [`licenseKey`](#licensekey) below). While the key is being checked, `BindingBuilder` renders a "Verifying license…" placeholder; if it's invalid, it renders an error message instead of the editor.
|
|
679
748
|
|
|
680
749
|
Current behavior, Object tab:
|
|
681
750
|
|
|
682
|
-
- upload a `.glb`, standalone/data-URI `.gltf`, or `.zip`
|
|
751
|
+
- upload a `.glb`, standalone/data-URI `.gltf`, `.obj`, `.fbx`, or `.usdz` 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)
|
|
683
752
|
- traverse renderable mesh nodes and generate starter bindings automatically
|
|
684
753
|
- edit binding identity, label, type, status, booleans, style, actions, metrics, metadata, and saved camera state
|
|
685
754
|
- 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
|
|
@@ -691,6 +760,8 @@ Current behavior, Object tab:
|
|
|
691
760
|
Current behavior, Scene tab:
|
|
692
761
|
|
|
693
762
|
- configure the same `SceneConfig` shape `ModelViewer`'s `sceneConfig` prop accepts (lighting, environment, background, ground shadows, wireframe, post-processing, animations, annotations)
|
|
763
|
+
- configure auto-rotation and its speed, exported as `sceneConfig.model.autoRotate` and `sceneConfig.model.autoRotateSpeed`
|
|
764
|
+
- position and rotate directional, point, and spot lights with transform gizmos; spot lights also expose angle and penumbra controls
|
|
694
765
|
- undo/redo the scene config independently of the Object tab's bindings history
|
|
695
766
|
- import a previously exported `sceneConfig.json`, merged by top-level section
|
|
696
767
|
- export the current scene config as JSON or TypeScript
|
|
@@ -698,6 +769,13 @@ Current behavior, Scene tab:
|
|
|
698
769
|
Preview:
|
|
699
770
|
|
|
700
771
|
- the selected node previews live in an embedded `ModelViewer`
|
|
772
|
+
|
|
773
|
+
Autosave:
|
|
774
|
+
|
|
775
|
+
- 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
|
|
776
|
+
- **Restore last session** in the preview toolbar reopens it; one session is kept per browser
|
|
777
|
+
- while there are edits on screen, leaving or reloading the page asks for confirmation
|
|
778
|
+
- if the browser refuses to store a session, usually for lack of space, the editor says so; export your work to keep it
|
|
701
779
|
- 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`
|
|
702
780
|
|
|
703
781
|
### `BindingBuilder` licensing & plan tiers
|
|
@@ -719,7 +797,8 @@ A locked feature renders in place (not hidden) with a message naming the require
|
|
|
719
797
|
Basic usage:
|
|
720
798
|
|
|
721
799
|
```tsx
|
|
722
|
-
import
|
|
800
|
+
import "@liveroom-tech/react-immersive/simple-model-viewer.css";
|
|
801
|
+
import { SimpleModelViewer } from "@liveroom-tech/react-immersive/simple-model-viewer";
|
|
723
802
|
|
|
724
803
|
export default function App() {
|
|
725
804
|
return (
|
|
@@ -791,7 +870,7 @@ const {
|
|
|
791
870
|
- `setCameraTarget`: sets the camera target to a given `[x, y, z]`
|
|
792
871
|
- `setCameraState`: restores a full camera state (`position`/`target`/`fov`/`zoom`) previously read from `cameraState`
|
|
793
872
|
- `lookAt`: moves the position and target together
|
|
794
|
-
- `orbitTo`:
|
|
873
|
+
- `orbitTo`: orbits to azimuth and polar angles in degrees (azimuth 0 is the front, 90 the right; polar 0 is straight down, 90 level)
|
|
795
874
|
- `dollyTo`: moves to a distance from the current target
|
|
796
875
|
- `savePreset`: captures the current camera under a name
|
|
797
876
|
- `goToPreset`: moves to a named camera preset
|
|
@@ -871,35 +950,96 @@ const { clips, currentClip, isPlaying, play, pause, stop, setSpeed } =
|
|
|
871
950
|
))}
|
|
872
951
|
```
|
|
873
952
|
|
|
953
|
+
## Presets and grouped props
|
|
954
|
+
|
|
955
|
+
`preset` sets `ModelViewer` up for a common kind of page:
|
|
956
|
+
|
|
957
|
+
| Preset | For | What it sets |
|
|
958
|
+
| --- | --- | --- |
|
|
959
|
+
| `minimal` | Just the model | Hides both panels, the download and reset buttons, and tour controls |
|
|
960
|
+
| `showcase` | A product on display | Everything `minimal` does, plus auto-rotation |
|
|
961
|
+
| `configurator` | Customers changing parts | Shows the selected object's panel and the reset button; hides the scene objects panel and download |
|
|
962
|
+
| `inspector` | Checking a model | Every panel, the view gizmo, the measure, UV checker, and exploded-view tools, and keyboard navigation |
|
|
963
|
+
|
|
964
|
+
`MODEL_VIEWER_PRESETS` lists exactly which props each preset sets.
|
|
965
|
+
|
|
966
|
+
Related props are also available as groups, which keeps a configured viewer
|
|
967
|
+
readable:
|
|
968
|
+
|
|
969
|
+
```tsx
|
|
970
|
+
<ModelViewer
|
|
971
|
+
modelUrl="/car.glb"
|
|
972
|
+
licenseKey="your-license-key"
|
|
973
|
+
preset="configurator"
|
|
974
|
+
ui={{ download: "car.glb" }}
|
|
975
|
+
tools={{ measure: { unit: "cm" } }}
|
|
976
|
+
controls={{ autoRotate: 0.5, zoom: false, limits: { maxDistance: 20 } }}
|
|
977
|
+
perf={{ profile: "low" }}
|
|
978
|
+
xr={{ enabled: true, usdzUrl: "/car.usdz" }}
|
|
979
|
+
/>
|
|
980
|
+
```
|
|
981
|
+
|
|
982
|
+
| Group | Fields, and the flat prop each one sets |
|
|
983
|
+
| --- | --- |
|
|
984
|
+
| `ui` | `objectPanel` (`showObjectBindingDataPanel`), `sceneObjects` (`showSceneObjectsPanel`), `download` (`showDownloadButton`; a string also sets `downloadFilename`), `reset` (`showResetButton`), `loadingOverlay` (`showLoadingOverlay`), `viewGizmo` (`showViewGizmo`), `mouseController` (`showMouseController`; `{ position, opacity }` also sets `mouseControllerPosition` and `mouseControllerOpacity`), `annotationNavigation` (`showAnnotationNavigation`), `annotationOnHover` (`showAnnotationOnHover`), `highlightOnHover` |
|
|
985
|
+
| `tools` | `measure` (`showMeasureTools`; `{ unit }` also sets `measurementUnit`), `uvChecker` (`showUvCheckerButton`), `explode` (`showExplodeControls`), `texturePositioning` (`texturePositioning`) |
|
|
986
|
+
| `controls` | `enabled` (`enableCameraControls`), `keyboard` (`enableKeyboardNavigation`), `zoom` (the opposite of `disableZoom`), `zoomOnSelect` (`zoomOnSelected`), `autoRotate` (a number also sets `autoRotateSpeed`), `refitOnResize`, `limits` (`cameraLimits`), `moveSensitivity`, `zoomSensitivity` |
|
|
987
|
+
| `perf` | `renderMode`, `maxDpr`, `profile` (`performanceProfile`), `preserveDrawingBuffer` |
|
|
988
|
+
| `tiles` | `url` (`tilesetUrl`), `errorTarget` (`tilesErrorTarget`), `cacheSize` (`tilesCacheSize`) |
|
|
989
|
+
| `xr` | `enabled` (`enableXR`), `usdzUrl`, `scaleMode` (`arScaleMode`), `mobileHandoffUrl` |
|
|
990
|
+
| `decoders` | `draco` (`dracoDecoderPath`), `ktx2` (`ktx2TranscoderPath`), `meshopt` |
|
|
991
|
+
|
|
992
|
+
A preset only fills in defaults: a flat prop overrides it, and a grouped prop
|
|
993
|
+
overrides both. The flat props keep working exactly as before.
|
|
994
|
+
|
|
874
995
|
## Public API
|
|
875
996
|
|
|
876
997
|
The source code currently defines `ModelViewer` with these props:
|
|
877
998
|
|
|
878
999
|
```ts
|
|
879
|
-
type ObjectTransformSpace = "local" | "world";
|
|
880
|
-
|
|
881
1000
|
type Props = {
|
|
1001
|
+
preset?: "minimal" | "showcase" | "configurator" | "inspector";
|
|
1002
|
+
ui?: ModelViewerUiOptions;
|
|
1003
|
+
tools?: ModelViewerToolsOptions;
|
|
1004
|
+
controls?: ModelViewerControlsOptions;
|
|
1005
|
+
perf?: ModelViewerPerfOptions;
|
|
1006
|
+
tiles?: ModelViewerTilesOptions;
|
|
1007
|
+
xr?: ModelViewerXrOptions;
|
|
1008
|
+
decoders?: ModelViewerDecoderOptions;
|
|
882
1009
|
modelUrl: string;
|
|
1010
|
+
tilesetUrl?: string | null;
|
|
1011
|
+
tilesErrorTarget?: number;
|
|
1012
|
+
tilesCacheSize?: number;
|
|
1013
|
+
onObjectCatalog?: (objects: ViewerObjectInfo[]) => void;
|
|
883
1014
|
modelFormat?: "glb" | "gltf" | "obj" | "fbx" | "usdz";
|
|
884
1015
|
licenseKey: string;
|
|
885
|
-
objectBindings
|
|
1016
|
+
objectBindings?: Record<string, ObjectBinding> | "auto"; // default "auto"
|
|
886
1017
|
selectedObject?: ObjectBinding | null;
|
|
1018
|
+
panelHoveredObject?: ObjectBinding | null;
|
|
887
1019
|
onObjectBindingsChange?: (next: Record<string, ObjectBinding>) => void;
|
|
888
1020
|
onObjectSelect?: (binding: ObjectBinding | null) => void;
|
|
889
1021
|
onObjectHover?: (binding: ObjectBinding | null) => void;
|
|
890
|
-
|
|
1022
|
+
onObjectDoubleClick?: (event: ObjectPointerEvent) => boolean | void;
|
|
1023
|
+
onObjectContextMenu?: (event: ObjectPointerEvent) => void;
|
|
1024
|
+
onModelLoaded?: (model: ViewerModelInfo) => void;
|
|
891
1025
|
onLoadError?: (error: unknown) => void;
|
|
1026
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
1027
|
+
cameraControllerType?: "orbit" | "pointerLock";
|
|
1028
|
+
onCameraControllerChange?: (
|
|
1029
|
+
controller: "orbit" | "pointerLock",
|
|
1030
|
+
) => void;
|
|
892
1031
|
onAction?: (event: ObjectActionEvent) => void;
|
|
893
1032
|
onHiddenObjectsChange?: (next: Record<string, boolean>) => void;
|
|
894
|
-
onCameraChange?: (
|
|
1033
|
+
onCameraChange?: (state: ViewerCameraState) => void;
|
|
895
1034
|
onViewerReady?: (viewer: ViewerReadyState) => void;
|
|
896
1035
|
onTextureUpload?: (file: File, objectId: string) => Promise<string>;
|
|
1036
|
+
texturePositioning?: boolean | TexturePositioningControls;
|
|
897
1037
|
onAnimationsReady?: (controls: AnimationControls) => void;
|
|
898
1038
|
onAnnotationsChange?: (annotations: AnnotationMarker[]) => void;
|
|
899
1039
|
activeAnnotation?: AnnotationMarker | null;
|
|
900
1040
|
onActiveAnnotationChange?: (annotation: AnnotationMarker | null) => void;
|
|
901
|
-
|
|
902
|
-
|
|
1041
|
+
camera?: ViewerCameraConfig;
|
|
1042
|
+
cameraLimits?: CameraLimits;
|
|
903
1043
|
backgroundColor?: string;
|
|
904
1044
|
shadows?: boolean;
|
|
905
1045
|
showObjectBindingDataPanel?: boolean;
|
|
@@ -929,9 +1069,10 @@ type Props = {
|
|
|
929
1069
|
sceneConfig?: SceneConfig;
|
|
930
1070
|
disableZoom?: boolean;
|
|
931
1071
|
zoomOnSelected?: boolean;
|
|
1072
|
+
highlightOnHover?: boolean;
|
|
932
1073
|
enableCameraControls?: boolean;
|
|
933
1074
|
moveModeEnabled?: boolean;
|
|
934
|
-
objectTransformSpace?:
|
|
1075
|
+
objectTransformSpace?: "local" | "world";
|
|
935
1076
|
enableKeyboardNavigation?: boolean;
|
|
936
1077
|
onAutoFit?: () => Promise<boolean>;
|
|
937
1078
|
refitOnResize?: boolean;
|
|
@@ -953,7 +1094,12 @@ type Props = {
|
|
|
953
1094
|
mobileHandoffUrl?: string;
|
|
954
1095
|
showAnnotationNavigation?: boolean;
|
|
955
1096
|
showAnnotationOnHover?: boolean;
|
|
1097
|
+
onSceneConfigChange?: (config: SceneConfig) => void;
|
|
956
1098
|
showViewGizmo?: boolean;
|
|
1099
|
+
overlay?: React.ReactNode;
|
|
1100
|
+
unsafe_sceneChildren?: React.ReactNode;
|
|
1101
|
+
autoRotate?: boolean;
|
|
1102
|
+
autoRotateSpeed?: number;
|
|
957
1103
|
};
|
|
958
1104
|
```
|
|
959
1105
|
|
|
@@ -972,13 +1118,44 @@ modelUrl = "/model.glb";
|
|
|
972
1118
|
Optional explicit format for signed or extensionless model URLs. Normal URLs
|
|
973
1119
|
ending in `.glb`, `.gltf`, `.obj`, `.fbx`, or `.usdz` are detected automatically.
|
|
974
1120
|
|
|
1121
|
+
### 3D Tiles streaming
|
|
1122
|
+
|
|
1123
|
+
Use `tilesetUrl` to stream a processed 3D Tiles model instead of downloading
|
|
1124
|
+
`modelUrl` as one monolithic asset. `modelUrl` remains required by the component
|
|
1125
|
+
API, but `tilesetUrl` is the render source while streaming is enabled.
|
|
1126
|
+
|
|
1127
|
+
```tsx
|
|
1128
|
+
<ModelViewer
|
|
1129
|
+
modelUrl="/models/campus.glb"
|
|
1130
|
+
tilesetUrl="/models/campus/tileset.json"
|
|
1131
|
+
tilesErrorTarget={8}
|
|
1132
|
+
tilesCacheSize={800}
|
|
1133
|
+
onObjectCatalog={(objects) => console.log(objects)}
|
|
1134
|
+
{...props}
|
|
1135
|
+
/>
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
- `tilesErrorTarget` controls the screen-space error target. Lower values load
|
|
1139
|
+
sharper tiles and use more bandwidth/GPU memory. Default: `8`.
|
|
1140
|
+
- `tilesCacheSize` is the maximum number of parsed tiles kept in the client
|
|
1141
|
+
cache. Default: `800`.
|
|
1142
|
+
- `onObjectCatalog` receives the bindable-object catalog from `objects.json`
|
|
1143
|
+
before all referenced tiles have loaded. See [`onObjectCatalog`](#onobjectcatalog).
|
|
1144
|
+
|
|
1145
|
+
Bindings, selection, hover, material overrides, transforms, exploded view, and
|
|
1146
|
+
scene renderer modes use the same public API in streamed and monolithic modes.
|
|
1147
|
+
The tileset and its tile payloads must be browser-accessible, and the optional
|
|
1148
|
+
object catalog must sit beside `tileset.json` as `objects.json`.
|
|
1149
|
+
|
|
975
1150
|
### `licenseKey`
|
|
976
1151
|
|
|
977
1152
|
License key for the library. Required.
|
|
978
1153
|
|
|
979
1154
|
### `objectBindings`
|
|
980
1155
|
|
|
981
|
-
A map keyed by the model node name
|
|
1156
|
+
A map keyed by the model node name, or `"auto"`, the default (see
|
|
1157
|
+
[Generating bindings automatically](#generating-bindings-automatically)). Each
|
|
1158
|
+
key should match a mesh name from the GLB/GLTF file.
|
|
982
1159
|
|
|
983
1160
|
`objectBindings` is the single source of truth for all visual state. Changes to the binding record drive rendering:
|
|
984
1161
|
|
|
@@ -1024,6 +1201,69 @@ const objectBindings = defineObjectBindings({
|
|
|
1024
1201
|
|
|
1025
1202
|
`defineObjectBindings` validates the complete record while preserving its exact keys and literal values. Use `ObjectBindingKey<typeof objectBindings>` for the binding-key union and `ObjectBindingActionId<typeof objectBindings, "Object_2">` for that binding's action-id union. It returns the input unchanged at runtime.
|
|
1026
1203
|
|
|
1204
|
+
If a binding's `modelObjectId` (or its key) doesn't match any node in the
|
|
1205
|
+
loaded model, that binding can never be selected. `ModelViewer` then shows a
|
|
1206
|
+
dismissible banner with the number of mismatched bindings, and logs a console
|
|
1207
|
+
warning naming them. Log [`onObjectCatalog`](#onobjectcatalog) to see the
|
|
1208
|
+
model's node names.
|
|
1209
|
+
|
|
1210
|
+
#### Generating bindings automatically
|
|
1211
|
+
|
|
1212
|
+
`objectBindings` defaults to `"auto"`: leave it out and `ModelViewer` generates
|
|
1213
|
+
a binding for every mesh when the model loads, so you don't need to know the
|
|
1214
|
+
node names up front:
|
|
1215
|
+
|
|
1216
|
+
```tsx
|
|
1217
|
+
<ModelViewer modelUrl="/model.glb" licenseKey="your-license-key" />
|
|
1218
|
+
```
|
|
1219
|
+
|
|
1220
|
+
Each generated binding is keyed by the node name, with a readable `label`
|
|
1221
|
+
(`"Wheel_FL"` becomes `"Wheel FL"`), an inferred `type`, a unique `id`, and
|
|
1222
|
+
built-in actions: `change-color`, `change-material`, and `toggle-visibility`,
|
|
1223
|
+
or `toggle-light`, `change-light-color`, and `set-brightness` for meshes whose
|
|
1224
|
+
names contain "light". For a streamed model (`tilesetUrl`), the bindings come
|
|
1225
|
+
from the object catalog, so objects are bindable before their tiles load.
|
|
1226
|
+
|
|
1227
|
+
The generated bindings are passed to `onObjectBindingsChange` once they exist,
|
|
1228
|
+
and again after every edit. To keep them, and to refine them later, store them
|
|
1229
|
+
and pass them back:
|
|
1230
|
+
|
|
1231
|
+
```tsx
|
|
1232
|
+
const [objectBindings, setObjectBindings] = useState<
|
|
1233
|
+
Record<string, ObjectBinding> | "auto"
|
|
1234
|
+
>("auto");
|
|
1235
|
+
|
|
1236
|
+
<ModelViewer
|
|
1237
|
+
modelUrl={modelUrl}
|
|
1238
|
+
licenseKey="your-license-key"
|
|
1239
|
+
objectBindings={objectBindings}
|
|
1240
|
+
onObjectBindingsChange={setObjectBindings}
|
|
1241
|
+
/>;
|
|
1242
|
+
```
|
|
1243
|
+
|
|
1244
|
+
After the first change the viewer is controlled by your state as usual. Set the
|
|
1245
|
+
state back to `"auto"` when you switch to a different model. While the prop is
|
|
1246
|
+
`"auto"`, bindings the same model already had are kept when it reloads, so
|
|
1247
|
+
runtime edits survive; a different `modelUrl` starts from fresh bindings.
|
|
1248
|
+
|
|
1249
|
+
The same defaults are available as functions from both entry points (they are
|
|
1250
|
+
also what `BindingBuilder` generates):
|
|
1251
|
+
|
|
1252
|
+
```ts
|
|
1253
|
+
import {
|
|
1254
|
+
buildBindingsFromNodes,
|
|
1255
|
+
createDefaultBinding,
|
|
1256
|
+
inferType,
|
|
1257
|
+
} from "@liveroom-tech/react-immersive/utils";
|
|
1258
|
+
|
|
1259
|
+
const bindings = buildBindingsFromNodes(["CarBody", "Wheel_FL"]);
|
|
1260
|
+
const door = { ...createDefaultBinding("FrontDoor"), label: "Front door" };
|
|
1261
|
+
inferType("Wheel_FL"); // "wheel"
|
|
1262
|
+
```
|
|
1263
|
+
|
|
1264
|
+
Every generated action is one `ModelViewer` performs itself, so every
|
|
1265
|
+
generated button works without extra setup.
|
|
1266
|
+
|
|
1027
1267
|
### `selectedObject`
|
|
1028
1268
|
|
|
1029
1269
|
Optional currently selected object binding, or `null` when nothing is selected.
|
|
@@ -1033,6 +1273,13 @@ If omitted, `ModelViewer` manages its own selection state internally.
|
|
|
1033
1273
|
|
|
1034
1274
|
When the selected object has a `cameraState` with both `position` and `target`, the viewer uses that saved camera view for focus/select behavior instead of falling back to `fitToBox`.
|
|
1035
1275
|
|
|
1276
|
+
### `panelHoveredObject`
|
|
1277
|
+
|
|
1278
|
+
Optional externally controlled binding to highlight as hovered by a custom
|
|
1279
|
+
scene-objects panel. It takes precedence over the built-in panel and canvas
|
|
1280
|
+
hover while `highlightOnHover` is enabled. Pass `null` when no panel row is
|
|
1281
|
+
hovered.
|
|
1282
|
+
|
|
1036
1283
|
### `onObjectSelect`
|
|
1037
1284
|
|
|
1038
1285
|
Optional callback called when the user clicks a mesh or closes the side panel.
|
|
@@ -1053,6 +1300,47 @@ Behavior:
|
|
|
1053
1300
|
- pointer over a mesh calls `onObjectHover(binding)`
|
|
1054
1301
|
- pointer out calls `onObjectHover(null)`
|
|
1055
1302
|
|
|
1303
|
+
### `onObjectDoubleClick`
|
|
1304
|
+
|
|
1305
|
+
Optional callback called when the user double-clicks an object.
|
|
1306
|
+
|
|
1307
|
+
```ts
|
|
1308
|
+
onObjectDoubleClick?: (event: ObjectPointerEvent) => boolean | void
|
|
1309
|
+
|
|
1310
|
+
type ObjectPointerEvent = {
|
|
1311
|
+
binding: ObjectBinding;
|
|
1312
|
+
clientX: number; // viewport coordinates
|
|
1313
|
+
clientY: number;
|
|
1314
|
+
pointerType: string; // "mouse" | "pen" | "touch"
|
|
1315
|
+
};
|
|
1316
|
+
```
|
|
1317
|
+
|
|
1318
|
+
Return `true` to say you handled it: the viewer then skips its own double-click
|
|
1319
|
+
behavior, which moves the camera's orbit point to that spot (or starts texture
|
|
1320
|
+
positioning, when `texturePositioning` is on). Return nothing to keep it.
|
|
1321
|
+
|
|
1322
|
+
### `onObjectContextMenu`
|
|
1323
|
+
|
|
1324
|
+
Optional callback called when the user right-clicks an object, or long-presses
|
|
1325
|
+
it on a touch screen. Use it to open your own menu:
|
|
1326
|
+
|
|
1327
|
+
```tsx
|
|
1328
|
+
<ModelViewer
|
|
1329
|
+
{...props}
|
|
1330
|
+
onObjectContextMenu={({ binding, clientX, clientY }) =>
|
|
1331
|
+
openMenu({ for: binding.id, x: clientX, y: clientY })
|
|
1332
|
+
}
|
|
1333
|
+
/>
|
|
1334
|
+
```
|
|
1335
|
+
|
|
1336
|
+
It receives the same `ObjectPointerEvent` as `onObjectDoubleClick`, with the
|
|
1337
|
+
position where the gesture started, ready for a `position: fixed` menu. A
|
|
1338
|
+
right-click fires when the button is released without the pointer moving, so a
|
|
1339
|
+
right-drag still pans the camera. A long-press fires after half a second, and
|
|
1340
|
+
not if the finger moves or a second finger touches down (a pinch); lifting the
|
|
1341
|
+
finger afterwards doesn't select the object. Objects with `selectable: false`
|
|
1342
|
+
don't fire it.
|
|
1343
|
+
|
|
1056
1344
|
### `onHiddenObjectsChange`
|
|
1057
1345
|
|
|
1058
1346
|
Optional callback fired with the derived hidden-object map whenever the current binding data changes hidden visibility state.
|
|
@@ -1071,16 +1359,56 @@ This fires for:
|
|
|
1071
1359
|
|
|
1072
1360
|
If you want the viewer and your app state to stay in sync, pass `objectBindings` from state and wire this callback back into that state setter.
|
|
1073
1361
|
|
|
1074
|
-
### `
|
|
1362
|
+
### `onObjectCatalog`
|
|
1075
1363
|
|
|
1076
|
-
Optional callback
|
|
1364
|
+
Optional callback listing every bindable object in the model: each named mesh,
|
|
1365
|
+
under the identifier `objectBindings` is keyed by. It's the quickest way to find
|
|
1366
|
+
out what your meshes are called:
|
|
1077
1367
|
|
|
1078
|
-
|
|
1368
|
+
```tsx
|
|
1369
|
+
<ModelViewer
|
|
1370
|
+
modelUrl="/model.glb"
|
|
1371
|
+
licenseKey="your-license-key"
|
|
1372
|
+
onObjectCatalog={(objects) => console.table(objects)}
|
|
1373
|
+
/>
|
|
1374
|
+
```
|
|
1375
|
+
|
|
1376
|
+
It fires each time a model loads. For a streamed model (`tilesetUrl`) the list
|
|
1377
|
+
comes from `objects.json` beside `tileset.json`, before the tiles containing
|
|
1378
|
+
those objects have necessarily loaded. Each entry is a `ViewerObjectInfo`:
|
|
1079
1379
|
|
|
1080
1380
|
```ts
|
|
1081
|
-
|
|
1381
|
+
type ViewerObjectInfo = {
|
|
1382
|
+
name: string; // the key to use in objectBindings
|
|
1383
|
+
triangles: number;
|
|
1384
|
+
minimum: [number, number, number];
|
|
1385
|
+
maximum: [number, number, number];
|
|
1386
|
+
};
|
|
1082
1387
|
```
|
|
1083
1388
|
|
|
1389
|
+
`minimum` and `maximum` are the object's bounds in the model's own units, in
|
|
1390
|
+
Z-up coordinates as in 3D Tiles: a point at glTF `(x, y, z)` is `(x, -z, y)`
|
|
1391
|
+
here. They're the same for loaded and streamed models, and don't change when the
|
|
1392
|
+
viewer moves or scales the model. `TileObjectCatalogEntry` is a deprecated
|
|
1393
|
+
alias of this type. To keep the list in state, use `useViewerModel` and pass
|
|
1394
|
+
its `handleObjectCatalog`.
|
|
1395
|
+
|
|
1396
|
+
### `onModelLoaded`
|
|
1397
|
+
|
|
1398
|
+
Optional callback fired after the model has loaded, with its bounds and mesh
|
|
1399
|
+
count:
|
|
1400
|
+
|
|
1401
|
+
```ts
|
|
1402
|
+
(model: ViewerModelInfo) => void
|
|
1403
|
+
|
|
1404
|
+
type ViewerModelInfo = {
|
|
1405
|
+
bounds: { min: Vec3; max: Vec3; center: Vec3; size: Vec3 }; // world space
|
|
1406
|
+
objectCount: number; // meshes in the loaded scene
|
|
1407
|
+
};
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
For the model's object names, use [`onObjectCatalog`](#onobjectcatalog).
|
|
1411
|
+
|
|
1084
1412
|
### `onLoadError`
|
|
1085
1413
|
|
|
1086
1414
|
Optional callback fired if the model fails to load or render.
|
|
@@ -1091,6 +1419,22 @@ Current callback shape:
|
|
|
1091
1419
|
(error: unknown) => void
|
|
1092
1420
|
```
|
|
1093
1421
|
|
|
1422
|
+
Whether or not you pass it, `ModelViewer` shows a message in place of the
|
|
1423
|
+
model that explains the likely cause: a missing file (404), refused access
|
|
1424
|
+
(401/403), a blocked cross-origin request (CORS), a web page served instead of
|
|
1425
|
+
a model, or a file that isn't the expected format. It includes the URL and the
|
|
1426
|
+
underlying error. Set `showLoadingOverlay={false}` to show your own instead.
|
|
1427
|
+
|
|
1428
|
+
### `onProgress`
|
|
1429
|
+
|
|
1430
|
+
Called with the loader's `ProgressEvent` while a monolithic model is being
|
|
1431
|
+
downloaded. When `event.lengthComputable` is true, progress is
|
|
1432
|
+
`event.loaded / event.total`; the built-in loading overlay uses the same value.
|
|
1433
|
+
|
|
1434
|
+
```ts
|
|
1435
|
+
onProgress?: (event: ProgressEvent) => void
|
|
1436
|
+
```
|
|
1437
|
+
|
|
1094
1438
|
### `onAction`
|
|
1095
1439
|
|
|
1096
1440
|
Optional callback fired when a built-in action button is clicked from the side panel.
|
|
@@ -1123,11 +1467,15 @@ Current callback shape:
|
|
|
1123
1467
|
|
|
1124
1468
|
```ts
|
|
1125
1469
|
type ViewerReadyState = {
|
|
1126
|
-
controls: CameraControls;
|
|
1127
|
-
scene: Object3D | null;
|
|
1128
1470
|
objectBindings: Record<string, ObjectBinding>;
|
|
1129
|
-
|
|
1471
|
+
homeCameraState: { position: Vec3; target: Vec3 } | null;
|
|
1130
1472
|
captureImage: (options?: CaptureImageOptions) => Promise<string>;
|
|
1473
|
+
invalidate: () => void;
|
|
1474
|
+
raw: {
|
|
1475
|
+
controls: CameraControls; // camera-controls
|
|
1476
|
+
scene: Object3D | null; // three.js
|
|
1477
|
+
nodes: Record<string, Object3D>; // model nodes by binding key and name
|
|
1478
|
+
};
|
|
1131
1479
|
};
|
|
1132
1480
|
|
|
1133
1481
|
type CaptureImageOptions = {
|
|
@@ -1137,6 +1485,11 @@ type CaptureImageOptions = {
|
|
|
1137
1485
|
};
|
|
1138
1486
|
```
|
|
1139
1487
|
|
|
1488
|
+
`raw` holds the live three.js scene, the camera-controls instance, and the
|
|
1489
|
+
model's nodes, for integrations the rest of the API doesn't cover. You don't
|
|
1490
|
+
need it for anything the hooks and props already do, and its shapes are those
|
|
1491
|
+
libraries' own, so they can change when those libraries do.
|
|
1492
|
+
|
|
1140
1493
|
`captureImage` requires the opt-in `preserveDrawingBuffer` prop, forces a render, and resolves with a `data:image/png` URL. Pass `width`/`height` to render at a resolution other than the canvas's current size, and `transparent` to hide the configured background so the PNG carries an alpha channel instead. It rejects if called before the viewer is ready or when framebuffer preservation is disabled. The Download PNG menu item is also shown only when this prop is enabled.
|
|
1141
1494
|
|
|
1142
1495
|
```tsx
|
|
@@ -1205,6 +1558,41 @@ Example:
|
|
|
1205
1558
|
/>
|
|
1206
1559
|
```
|
|
1207
1560
|
|
|
1561
|
+
### `texturePositioning`
|
|
1562
|
+
|
|
1563
|
+
Turns on texture positioning for objects with an uploaded texture. Off by
|
|
1564
|
+
default. When on, the **Change Material** panel gains Offset X/Y and Rotation
|
|
1565
|
+
controls, and double-clicking the object puts the viewer in Move/Zoom mode:
|
|
1566
|
+
drag to move the texture, scroll to scale it, and click the canvas or
|
|
1567
|
+
**Done positioning** to finish.
|
|
1568
|
+
|
|
1569
|
+
```tsx
|
|
1570
|
+
<ModelViewer modelUrl="/sofa.glb" licenseKey="your-license-key" texturePositioning />
|
|
1571
|
+
```
|
|
1572
|
+
|
|
1573
|
+
Changes are written to the binding's `style.material.texture` (`offset`,
|
|
1574
|
+
`rotation` in radians, and `scale`), so they reach `onObjectBindingsChange`
|
|
1575
|
+
and your saved bindings like any other edit.
|
|
1576
|
+
|
|
1577
|
+
To handle some of it yourself, pass an object instead of `true`. Any field you
|
|
1578
|
+
leave out keeps the viewer's own behavior:
|
|
1579
|
+
|
|
1580
|
+
```ts
|
|
1581
|
+
type TexturePositioningControls = {
|
|
1582
|
+
offset?: [number, number]; // shown in the Offset fields instead of the binding's
|
|
1583
|
+
rotationDegrees?: number; // shown in the Rotation field, 0–360
|
|
1584
|
+
onOffsetChange?: (objectId: string, axisIndex: 0 | 1, value: number) => void;
|
|
1585
|
+
onRotationChange?: (objectId: string, degrees: number) => void;
|
|
1586
|
+
isPositioning?: boolean; // shows "Done positioning" instead of the hint
|
|
1587
|
+
onEndPositioning?: () => void; // after Done positioning ends the mode
|
|
1588
|
+
};
|
|
1589
|
+
```
|
|
1590
|
+
|
|
1591
|
+
`onOffsetChange` and `onRotationChange` replace the viewer's writes from the
|
|
1592
|
+
panel fields; dragging and scrolling on the canvas still write to the binding.
|
|
1593
|
+
|
|
1594
|
+
---
|
|
1595
|
+
|
|
1208
1596
|
### `onAnimationsReady`
|
|
1209
1597
|
|
|
1210
1598
|
Optional callback fired after the GLB/GLTF model is loaded, providing animation controls. If the model contains no animations, it is still called with an empty `clips` array and no-op control functions.
|
|
@@ -1244,38 +1632,69 @@ When `sceneConfig.animations.autoplayClip` is set, `ModelViewer` will auto-play
|
|
|
1244
1632
|
that clip on load and respect per-clip `loopMode`, `speed`, `displayName`, and
|
|
1245
1633
|
soft-delete (`hidden`) settings.
|
|
1246
1634
|
|
|
1247
|
-
###
|
|
1248
|
-
|
|
1249
|
-
Optional custom lighting to render inside the scene.
|
|
1635
|
+
### Lighting
|
|
1250
1636
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1637
|
+
Lighting lives in `sceneConfig.lighting`: an ambient light plus a list of
|
|
1638
|
+
directional, point, spot, and hemisphere lights. Without a `sceneConfig`,
|
|
1639
|
+
`ModelViewer` uses its default rig (ambient, a hemisphere fill, and a
|
|
1640
|
+
shadow-casting key light). To use your own lights, patch the default scene
|
|
1641
|
+
config. `sceneLight` fills in every setting you leave out:
|
|
1254
1642
|
|
|
1255
1643
|
```tsx
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
}
|
|
1644
|
+
import {
|
|
1645
|
+
DEFAULT_SCENE_CONFIG,
|
|
1646
|
+
patchSceneConfig,
|
|
1647
|
+
sceneLight,
|
|
1648
|
+
} from "@liveroom-tech/react-immersive/utils";
|
|
1649
|
+
|
|
1650
|
+
const sceneConfig = patchSceneConfig(DEFAULT_SCENE_CONFIG, {
|
|
1651
|
+
lighting: {
|
|
1652
|
+
ambient: { intensity: 0.5 },
|
|
1653
|
+
lights: [
|
|
1654
|
+
sceneLight({ id: "key", type: "directional", position: [4, 8, 4], intensity: 1.6, castShadow: true }),
|
|
1655
|
+
sceneLight({ id: "fill", type: "point", position: [-3, 3, 2], intensity: 0.8 }),
|
|
1656
|
+
],
|
|
1657
|
+
},
|
|
1658
|
+
});
|
|
1265
1659
|
|
|
1266
1660
|
<ModelViewer
|
|
1267
1661
|
modelUrl="/model.glb"
|
|
1268
1662
|
licenseKey="your-license-key"
|
|
1269
|
-
|
|
1270
|
-
lights={<CustomLights />}
|
|
1663
|
+
sceneConfig={sceneConfig}
|
|
1271
1664
|
/>;
|
|
1272
1665
|
```
|
|
1273
1666
|
|
|
1667
|
+
To change lights while the app runs, use the lights controller from
|
|
1668
|
+
`useViewer` (or `useSceneConfig`):
|
|
1669
|
+
|
|
1670
|
+
```tsx
|
|
1671
|
+
const viewer = useViewer({ sceneConfig });
|
|
1672
|
+
|
|
1673
|
+
viewer.scene.lights.setIntensity("key", 2.2);
|
|
1674
|
+
viewer.scene.lights.toggle("fill");
|
|
1675
|
+
viewer.scene.lights.add({ id: "bulb", type: "point", color: "#ffd27d", intensity: 4 });
|
|
1676
|
+
viewer.scene.lights.attach("bulb", { objectId: "Lamp", offset: [0, 1.4, 0] });
|
|
1677
|
+
```
|
|
1678
|
+
|
|
1679
|
+
An attached light follows the object, offset from the object's origin in its
|
|
1680
|
+
local space (the origin is often at its base, not its center). Lighting set up
|
|
1681
|
+
in `BindingBuilder`'s Scene tab exports as the same `sceneConfig`.
|
|
1682
|
+
|
|
1274
1683
|
### `camera`
|
|
1275
1684
|
|
|
1276
|
-
Optional
|
|
1685
|
+
Optional starting camera: where it is and its lens.
|
|
1686
|
+
|
|
1687
|
+
```ts
|
|
1688
|
+
type ViewerCameraConfig = {
|
|
1689
|
+
position?: [number, number, number];
|
|
1690
|
+
fov?: number; // vertical field of view in degrees; default 50
|
|
1691
|
+
near?: number;
|
|
1692
|
+
far?: number;
|
|
1693
|
+
zoom?: number;
|
|
1694
|
+
};
|
|
1695
|
+
```
|
|
1277
1696
|
|
|
1278
|
-
|
|
1697
|
+
Without a `position`, the camera starts at `[0, 0, 5]`. Pass one for predictable framing.
|
|
1279
1698
|
|
|
1280
1699
|
Example:
|
|
1281
1700
|
|
|
@@ -1295,6 +1714,50 @@ Example:
|
|
|
1295
1714
|
|
|
1296
1715
|
Passing an explicit `camera` counts as choosing the initial view yourself, so the mount-time auto-fit (see [`onAutoFit`](#onautofit)) is skipped automatically and your `position`/`fov` stick. Provide `onAutoFit` as well only if you want to run your _own_ fit-to-scene logic instead.
|
|
1297
1716
|
|
|
1717
|
+
### `cameraLimits`
|
|
1718
|
+
|
|
1719
|
+
Constrains how far the orbit camera can zoom, tilt, turn, and pan. Angles are
|
|
1720
|
+
in degrees.
|
|
1721
|
+
|
|
1722
|
+
```tsx
|
|
1723
|
+
<ModelViewer
|
|
1724
|
+
modelUrl="/car.glb"
|
|
1725
|
+
licenseKey="your-license-key"
|
|
1726
|
+
cameraLimits={{
|
|
1727
|
+
minDistance: 2,
|
|
1728
|
+
maxDistance: 12,
|
|
1729
|
+
maxPolarAngle: 85, // stay above the floor
|
|
1730
|
+
minAzimuthAngle: -60, // turn at most 60° either side of the front
|
|
1731
|
+
maxAzimuthAngle: 60,
|
|
1732
|
+
}}
|
|
1733
|
+
/>
|
|
1734
|
+
```
|
|
1735
|
+
|
|
1736
|
+
```ts
|
|
1737
|
+
type CameraLimits = {
|
|
1738
|
+
minDistance?: number; // default 0
|
|
1739
|
+
maxDistance?: number; // default Infinity
|
|
1740
|
+
minPolarAngle?: number; // degrees, default 0
|
|
1741
|
+
maxPolarAngle?: number; // degrees, default 180
|
|
1742
|
+
minAzimuthAngle?: number; // degrees, default -Infinity
|
|
1743
|
+
maxAzimuthAngle?: number; // degrees, default Infinity
|
|
1744
|
+
panBoundary?: [
|
|
1745
|
+
min: [number, number, number],
|
|
1746
|
+
max: [number, number, number],
|
|
1747
|
+
];
|
|
1748
|
+
boundaryEnclosesCamera?: boolean; // default false
|
|
1749
|
+
boundaryFriction?: number; // default 0
|
|
1750
|
+
};
|
|
1751
|
+
```
|
|
1752
|
+
|
|
1753
|
+
- **Polar** is the vertical angle: `0` looks straight down from above, `90` is
|
|
1754
|
+
level with the target, and `180` looks straight up from below.
|
|
1755
|
+
- **Azimuth** is the horizontal angle around the vertical axis: `0` views the
|
|
1756
|
+
target from the front (+Z), `90` from its right (+X), and `-90` from its left.
|
|
1757
|
+
|
|
1758
|
+
`panBoundary` is an axis-aligned world-space box. These limits apply to the
|
|
1759
|
+
orbit controller; pointer-lock mode is not orbiting around the same target.
|
|
1760
|
+
|
|
1298
1761
|
### `backgroundColor`
|
|
1299
1762
|
|
|
1300
1763
|
Optional background color for the viewer canvas.
|
|
@@ -1337,14 +1800,25 @@ Example:
|
|
|
1337
1800
|
|
|
1338
1801
|
### `onCameraChange`
|
|
1339
1802
|
|
|
1340
|
-
Optional callback fired whenever the camera
|
|
1341
|
-
|
|
1342
|
-
Current callback shape:
|
|
1803
|
+
Optional callback fired whenever the camera moves, with its plain state. It
|
|
1804
|
+
fires often while the user orbits, pans, or zooms.
|
|
1343
1805
|
|
|
1344
1806
|
```ts
|
|
1345
|
-
(
|
|
1807
|
+
(state: ViewerCameraState) => void
|
|
1808
|
+
|
|
1809
|
+
type ViewerCameraState = {
|
|
1810
|
+
position: [number, number, number];
|
|
1811
|
+
target: [number, number, number];
|
|
1812
|
+
fov: number | null;
|
|
1813
|
+
zoom: number;
|
|
1814
|
+
azimuthAngle?: number; // radians, for setCameraState to restore
|
|
1815
|
+
polarAngle?: number; // radians
|
|
1816
|
+
};
|
|
1346
1817
|
```
|
|
1347
1818
|
|
|
1819
|
+
It's the same state `useViewerCamera` exposes as `cameraState`, and
|
|
1820
|
+
`setCameraState` restores it.
|
|
1821
|
+
|
|
1348
1822
|
### `showObjectBindingDataPanel`
|
|
1349
1823
|
|
|
1350
1824
|
Optional boolean that controls whether the built-in left object details panel is rendered.
|
|
@@ -1373,6 +1847,57 @@ type CustomObjectBindingDataPanelProps = {
|
|
|
1373
1847
|
|
|
1374
1848
|
When provided, `ModelViewer` passes its existing internal handlers into your custom panel. Your panel can call them, wrap them, or ignore them.
|
|
1375
1849
|
|
|
1850
|
+
### `overlay`
|
|
1851
|
+
|
|
1852
|
+
Your own UI over the 3D view: badges, HUDs, buttons, custom panels. It sits
|
|
1853
|
+
above the model and below the viewer's toolbars and banners.
|
|
1854
|
+
|
|
1855
|
+
```tsx
|
|
1856
|
+
<ModelViewer
|
|
1857
|
+
modelUrl="/sofa.glb"
|
|
1858
|
+
licenseKey="your-license-key"
|
|
1859
|
+
overlay={
|
|
1860
|
+
<div style={{ position: "absolute", top: 16, left: 16 }}>
|
|
1861
|
+
In stock <button onClick={addToCart}>Add to cart</button>
|
|
1862
|
+
</div>
|
|
1863
|
+
}
|
|
1864
|
+
/>
|
|
1865
|
+
```
|
|
1866
|
+
|
|
1867
|
+
The layer covers the view but lets pointer events through, so the model stays
|
|
1868
|
+
draggable around your elements. Its direct children receive pointer events, so
|
|
1869
|
+
position them yourself (for example with `position: "absolute"`) rather than
|
|
1870
|
+
wrapping everything in one full-size container.
|
|
1871
|
+
|
|
1872
|
+
### `unsafe_sceneChildren`
|
|
1873
|
+
|
|
1874
|
+
react-three-fiber elements rendered inside the scene, for anything the viewer
|
|
1875
|
+
can't do itself, such as custom geometry, a helper, or an effect driven by
|
|
1876
|
+
`useFrame`:
|
|
1877
|
+
|
|
1878
|
+
```tsx
|
|
1879
|
+
import { useFrame } from "@react-three/fiber";
|
|
1880
|
+
|
|
1881
|
+
function Marker() {
|
|
1882
|
+
const ref = useRef<Mesh>(null);
|
|
1883
|
+
useFrame((_, delta) => (ref.current!.rotation.y += delta));
|
|
1884
|
+
return (
|
|
1885
|
+
<mesh ref={ref} position={[0, 1.5, 0]}>
|
|
1886
|
+
<boxGeometry args={[0.3, 0.3, 0.3]} />
|
|
1887
|
+
<meshStandardMaterial color="hotpink" />
|
|
1888
|
+
</mesh>
|
|
1889
|
+
);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
<ModelViewer {...props} unsafe_sceneChildren={<Marker />} />;
|
|
1893
|
+
```
|
|
1894
|
+
|
|
1895
|
+
They're rendered inside their own `Suspense` boundary, so a child that loads
|
|
1896
|
+
assets doesn't hold up the model. Because they work with three.js directly,
|
|
1897
|
+
they're outside the viewer's own API: the name says so, and they can break when
|
|
1898
|
+
three.js or react-three-fiber change. Prefer the props and hooks for anything
|
|
1899
|
+
they cover.
|
|
1900
|
+
|
|
1376
1901
|
### `customSceneObjectsPanel`
|
|
1377
1902
|
|
|
1378
1903
|
Optional render prop for replacing the built-in right scene objects panel.
|
|
@@ -1442,7 +1967,7 @@ true;
|
|
|
1442
1967
|
|
|
1443
1968
|
### `showLoadingOverlay`
|
|
1444
1969
|
|
|
1445
|
-
Optional boolean controlling whether the built-in loading overlay is shown while the model is loading and the initial camera fit is settling.
|
|
1970
|
+
Optional boolean controlling whether the built-in loading overlay is shown while the model is loading and the initial camera fit is settling, and the load-error message if the model fails to load (see [`onLoadError`](#onloaderror)).
|
|
1446
1971
|
|
|
1447
1972
|
Default:
|
|
1448
1973
|
|
|
@@ -1542,6 +2067,17 @@ magenta and trigger an explanatory notice.
|
|
|
1542
2067
|
/>
|
|
1543
2068
|
```
|
|
1544
2069
|
|
|
2070
|
+
### `onSceneConfigChange`
|
|
2071
|
+
|
|
2072
|
+
Called with an updated `SceneConfig` when viewer-owned scene editing changes
|
|
2073
|
+
the configuration. Currently this is used when a light-position or rotation
|
|
2074
|
+
gizmo moves a scene-config light. Update controlled `sceneConfig` state with
|
|
2075
|
+
the value returned by this callback.
|
|
2076
|
+
|
|
2077
|
+
```ts
|
|
2078
|
+
onSceneConfigChange?: (config: SceneConfig) => void
|
|
2079
|
+
```
|
|
2080
|
+
|
|
1545
2081
|
### `disableZoom`
|
|
1546
2082
|
|
|
1547
2083
|
Optional boolean. When `true`, the viewer never zooms the camera to an object on selection (selection still highlights and fires callbacks).
|
|
@@ -1562,6 +2098,12 @@ Default:
|
|
|
1562
2098
|
true;
|
|
1563
2099
|
```
|
|
1564
2100
|
|
|
2101
|
+
### `highlightOnHover`
|
|
2102
|
+
|
|
2103
|
+
Controls hover highlighting for the canvas, built-in object list, and
|
|
2104
|
+
`panelHoveredObject`. Default: `true`. On very large models, viewport hover is
|
|
2105
|
+
disabled for performance, while panel-row highlighting remains available.
|
|
2106
|
+
|
|
1565
2107
|
### Moving and rotating objects
|
|
1566
2108
|
|
|
1567
2109
|
Set `moveModeEnabled` to show a centered Drei `PivotControls` gizmo for the
|
|
@@ -1623,6 +2165,41 @@ is managed internally.
|
|
|
1623
2165
|
Optional boolean controlling orbit, pan, and zoom input. Default: `true`.
|
|
1624
2166
|
Camera controls are temporarily disabled whenever `moveModeEnabled` is on.
|
|
1625
2167
|
|
|
2168
|
+
### `cameraControllerType` / `onCameraControllerChange`
|
|
2169
|
+
|
|
2170
|
+
Selects the active camera interaction mode:
|
|
2171
|
+
|
|
2172
|
+
- `"orbit"` (default) uses orbit, pan, and zoom controls.
|
|
2173
|
+
- `"pointerLock"` captures the pointer for first-person look-around. Leaving
|
|
2174
|
+
pointer lock returns the viewer to orbit mode.
|
|
2175
|
+
|
|
2176
|
+
The viewer's camera-controller menu can manage this internally. Pass
|
|
2177
|
+
`cameraControllerType` to control it from application state and update that
|
|
2178
|
+
state from `onCameraControllerChange`.
|
|
2179
|
+
|
|
2180
|
+
```tsx
|
|
2181
|
+
const [controller, setController] = useState<"orbit" | "pointerLock">("orbit");
|
|
2182
|
+
|
|
2183
|
+
<ModelViewer
|
|
2184
|
+
{...props}
|
|
2185
|
+
cameraControllerType={controller}
|
|
2186
|
+
onCameraControllerChange={setController}
|
|
2187
|
+
/>
|
|
2188
|
+
```
|
|
2189
|
+
|
|
2190
|
+
Auto-rotation pauses while pointer-lock mode is active.
|
|
2191
|
+
|
|
2192
|
+
### `autoRotate` / `autoRotateSpeed`
|
|
2193
|
+
|
|
2194
|
+
Enables automatic orbiting and controls its speed. Defaults are `false` and
|
|
2195
|
+
`1`. When these props aren't passed, `sceneConfig.model.autoRotate` and
|
|
2196
|
+
`sceneConfig.model.autoRotateSpeed` apply, so a scene exported from
|
|
2197
|
+
`BindingBuilder` can carry the same behavior. When they are passed, they win.
|
|
2198
|
+
|
|
2199
|
+
```tsx
|
|
2200
|
+
<ModelViewer {...props} autoRotate autoRotateSpeed={0.75} />
|
|
2201
|
+
```
|
|
2202
|
+
|
|
1626
2203
|
### `enableKeyboardNavigation`
|
|
1627
2204
|
|
|
1628
2205
|
Optional boolean enabling camera shortcuts after the viewer canvas is clicked
|
|
@@ -2007,7 +2584,7 @@ type SimpleModelViewerProps = {
|
|
|
2007
2584
|
ambientColor?: string;
|
|
2008
2585
|
directionalIntensity?: number;
|
|
2009
2586
|
directionalColor?: string;
|
|
2010
|
-
onModelLoaded?: (
|
|
2587
|
+
onModelLoaded?: (model: ViewerModelInfo) => void;
|
|
2011
2588
|
onLoadError?: (error: unknown) => void;
|
|
2012
2589
|
};
|
|
2013
2590
|
```
|
|
@@ -2134,7 +2711,8 @@ false;
|
|
|
2134
2711
|
|
|
2135
2712
|
### `onModelLoaded`
|
|
2136
2713
|
|
|
2137
|
-
Optional callback fired after the
|
|
2714
|
+
Optional callback fired after the model has loaded, with its bounds and mesh
|
|
2715
|
+
count (`(model: ViewerModelInfo) => void`, the same payload as `ModelViewer`'s).
|
|
2138
2716
|
|
|
2139
2717
|
### `onLoadError`
|
|
2140
2718
|
|
|
@@ -2175,7 +2753,7 @@ type ObjectBindingTransform = {
|
|
|
2175
2753
|
|
|
2176
2754
|
type ObjectBinding = {
|
|
2177
2755
|
id: string;
|
|
2178
|
-
type
|
|
2756
|
+
type?: ObjectBindingType; // any string; suggestions: "body" | "light" | "wheel" | "wall" | "door" | "furniture" | ...; default "other"
|
|
2179
2757
|
modelObjectId: string;
|
|
2180
2758
|
label?: string;
|
|
2181
2759
|
group?: string;
|
|
@@ -2208,11 +2786,27 @@ Notes:
|
|
|
2208
2786
|
- `BindingBuilder` intentionally omits exact Sketchfab glossiness, cavity, and subsurface-scattering workflows; those require preprocessing or a different material pipeline
|
|
2209
2787
|
- `cameraState` stores a saved camera view for the object; when both `position` and `target` are set, the viewer uses that view when the object is focused or selected instead of falling back to `fitToBox`
|
|
2210
2788
|
- `metrics` and `metadata` are displayed in the side panel as nested key/value fields (objects expand into indented child labels)
|
|
2211
|
-
- `
|
|
2789
|
+
- `type` groups and filters bindings (for example `hideObject({ type: "wheel" })`). Any string works: `ObjectBindingType` suggests values for vehicles, rooms, furniture, characters, weapons, environment, and more, but a domain it doesn't cover can use its own (`"bone"`, `"turbine-blade"`). Left out, it counts as `"other"`
|
|
2212
2790
|
|
|
2213
2791
|
## Supported Built-In Actions
|
|
2214
2792
|
|
|
2215
|
-
|
|
2793
|
+
`ModelViewer` handles six action ids itself: `change-color`,
|
|
2794
|
+
`change-material`, `toggle-visibility`, `toggle-light`, `change-light-color`,
|
|
2795
|
+
and `set-brightness`. Any other id does something only when you handle it in
|
|
2796
|
+
`onAction` (pass `useViewerActions().handleAction` there to run an action's
|
|
2797
|
+
declared `effects`). If a binding has such an action and `ModelViewer` has no
|
|
2798
|
+
`onAction`, its button does nothing, and the viewer logs a console warning
|
|
2799
|
+
naming each one. Every action also reaches `onAction`, after the viewer's own
|
|
2800
|
+
handling.
|
|
2801
|
+
|
|
2802
|
+
`ACTION_CATALOG` lists the ids `BindingBuilder` offers, and whether the viewer
|
|
2803
|
+
or your app handles each one (`handling: "viewer" | "host"`):
|
|
2804
|
+
|
|
2805
|
+
```ts
|
|
2806
|
+
import { ACTION_CATALOG, findCatalogAction } from "@liveroom-tech/react-immersive/utils";
|
|
2807
|
+
|
|
2808
|
+
findCatalogAction("toggle-light")?.handling; // "viewer"
|
|
2809
|
+
```
|
|
2216
2810
|
|
|
2217
2811
|
### Implemented behaviors
|
|
2218
2812
|
|
|
@@ -2222,6 +2816,26 @@ Action handling is currently hardcoded in `ModelViewer`.
|
|
|
2222
2816
|
Opens the color picker inline beneath the action; the chosen hex is applied as a live preview while dragging and committed into `binding.style.material.baseColor` via `onObjectBindingsChange` when the picker closes
|
|
2223
2817
|
- `change-material`
|
|
2224
2818
|
Opens the texture upload panel inline beneath the action for JPG, PNG, and WebP files; if `onTextureUpload` is provided, the file is passed to it and the returned URL is stored in `binding.style.material.texture.path`; otherwise a session-scoped blob URL is used as the fallback
|
|
2819
|
+
- `toggle-light`
|
|
2820
|
+
Turns the object's light off, or back on
|
|
2821
|
+
- `change-light-color`
|
|
2822
|
+
Opens a color picker inline beneath the action that sets the light's color
|
|
2823
|
+
- `set-brightness`
|
|
2824
|
+
Opens a slider inline beneath the action that sets the light's brightness, from 0 to 200% of what it was when it opened
|
|
2825
|
+
|
|
2826
|
+
What "the object's light" is depends on the scene:
|
|
2827
|
+
|
|
2828
|
+
- If scene lights are attached to the object (in `BindingBuilder`'s Lighting
|
|
2829
|
+
tab, or with `useSceneConfig().lights.attach`), the actions control those
|
|
2830
|
+
lights, which light up the room around it. The changes go to
|
|
2831
|
+
`sceneConfig.lighting`: to `onSceneConfigChange` if you pass it (`useViewer`
|
|
2832
|
+
does, so `viewer.scene` stays current), otherwise `ModelViewer` keeps them
|
|
2833
|
+
itself, over whatever `sceneConfig` it's given.
|
|
2834
|
+
- Otherwise they control the object's own glow: its emissive color and strength,
|
|
2835
|
+
saved on the binding as `style.material.emissive` and
|
|
2836
|
+
`style.material.emissiveIntensity`, like any other edit. This works on any
|
|
2837
|
+
light fixture with no setup, but a glow doesn't light up the objects around
|
|
2838
|
+
it. Toggling off and back on restores the previous strength.
|
|
2225
2839
|
|
|
2226
2840
|
## How Rendering Works
|
|
2227
2841
|
|
|
@@ -2247,7 +2861,7 @@ When `shadows` is enabled (the default), the canvas renders soft shadow maps and
|
|
|
2247
2861
|
- per-mesh `castShadow`/`receiveShadow`, with one exception: the large enclosing "shell" mesh (walls/ceiling of an interior model) is detected by its bounding-box size and excluded from **casting** so a top-down light still reaches the interior, it continues to **receive** shadows, so furniture casts visible contact shadows onto the floor and walls
|
|
2248
2862
|
- a post-processing stack (`EffectComposer`) with SSAO ambient occlusion (normal pass enabled), bloom, vignette, and ACES filmic tone mapping
|
|
2249
2863
|
|
|
2250
|
-
Pass `shadows={false}` to disable shadow rendering entirely, or
|
|
2864
|
+
Pass `shadows={false}` to disable shadow rendering entirely, or set your own lights in `sceneConfig.lighting` (see [Lighting](#lighting)) to replace the default rig.
|
|
2251
2865
|
|
|
2252
2866
|
## UI Behavior
|
|
2253
2867
|
|