@liveroom-tech/react-immersive 3.0.0 → 4.0.1

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 CHANGED
@@ -1,6 +1,8 @@
1
- # `@liveroom-tech/react-immersive`
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 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.
3
+ `@liveroom-tech/react-immersive` is a React-based 3D model viewer for interactive GLB, GLTF, OBJ, FBX, and USDZ 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
+
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)
4
6
 
5
7
  ## What It Does
6
8
 
@@ -31,11 +33,12 @@ import {
31
33
  } from "@liveroom-tech/react-immersive";
32
34
  ```
33
35
 
34
- It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindingsController`, `ObjectActionEvent`, `SceneConfig`, `SceneConfigPatch`, `SceneConfigController`, `AnimationControls`, `CinematicConfig` / `CinematicWaypoint`, and related shapes used throughout this document.
36
+ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindingsController`, `ObjectActionEvent`, `ObjectTransformSpace`, `SceneConfig`, `SceneConfigPatch`, `SceneConfigController`, `AnimationControls`, `CinematicConfig` / `CinematicWaypoint`, and related shapes used throughout this document.
35
37
 
36
38
  `ModelViewer` provides:
37
39
 
38
- - GLB/GLTF model rendering through `@react-three/fiber` and `@react-three/drei`
40
+ - GLB/GLTF, OBJ/MTL, FBX, and USDZ model rendering through `@react-three/fiber`
41
+ - camera-driven 3D Tiles streaming through `tilesetUrl`, with configurable screen-space error and bounded tile cache
39
42
  - orbit/pan/zoom camera controls via `CameraControls`
40
43
  - 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
41
44
  - optional custom scene lighting through a `lights` prop
@@ -43,10 +46,13 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
43
46
  - optional custom background color through a `backgroundColor` prop
44
47
  - optional shadow rendering toggle through a `shadows` prop (on by default)
45
48
  - optional on-screen movement controller through `showMouseController` and its tuning props
49
+ - optional canvas-focused keyboard camera navigation through `enableKeyboardNavigation`
50
+ - optional top-right camera orientation control through `showViewGizmo`
51
+ - optional per-object move and rotate gizmo through `moveModeEnabled`, with World/Local axis alignment through `objectTransformSpace`
46
52
  - optional custom left object data panel through `customObjectBindingDataPanel`
47
53
  - optional custom right scene objects panel through `customSceneObjectsPanel`
48
54
  - optional hiding of either built-in panel through `showObjectBindingDataPanel` and `showSceneObjectsPanel`
49
- - optional model export button through `showDownloadButton` and `downloadFilename`, plus an independent `showResetButton` toggle for the rest of that action bar
55
+ - 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
50
56
  - an optional UV Checker toolbar button through `showUvCheckerButton`, for inspecting UV scale, stretching, seams, orientation, and missing UV0 coordinates directly in `ModelViewer`
51
57
  - click-to-measure distance tool and a bounding-box dimensions overlay through `showMeasureTools` and `measurementUnit`
52
58
  - an exploded-view slider that slides each bound part outward from the model center to reveal interior/assembly structure through `showExplodeControls`
@@ -72,14 +78,15 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
72
78
  - camera change callbacks through `onCameraChange`
73
79
  - viewer-ready callbacks through `onViewerReady`
74
80
  - action event callbacks through `onAction`
75
- - animation playback for GLB/GLTF models with embedded animations through `onAnimationsReady`
81
+ - animation playback for GLB/GLTF, FBX, and USDZ models with supported embedded animations through `onAnimationsReady`
76
82
  - annotation marker callbacks through `onAnnotationsChange` and controlled/uncontrolled `activeAnnotation` / `onActiveAnnotationChange`
77
83
 
78
84
  `BindingBuilder` provides:
79
85
 
80
- - a design-time UI for generating starter bindings from GLB/GLTF assets, gated by `licenseKey` (see "`BindingBuilder` licensing & plan tiers" below)
81
- - demo model loading or custom `.glb`, `.gltf`, or zipped GLTF upload
86
+ - a design-time UI for generating starter bindings from GLB, GLTF, OBJ, FBX, and USDZ assets, gated by `licenseKey` (see "`BindingBuilder` licensing & plan tiers" below)
87
+ - demo model loading or custom `.glb`, `.gltf`, `.obj`, `.fbx`, `.usdz`, or model ZIP bundle upload
82
88
  - editable binding fields for identity, basics, style, actions, metrics, metadata, and saved camera state
89
+ - per-object move and rotate authoring with a geometry-centered pivot, World/Local axes, numeric fields, and restore-to-authored-transform support
83
90
  - a one-click material preset gallery (wood, metal, chrome, glass, plastic, fabric, ceramic, concrete, etc.)
84
91
  - undo/redo for both the Object tab's bindings and the Scene tab's config, independently, with `Cmd/Ctrl+Z` / `Cmd/Ctrl+Shift+Z` shortcuts
85
92
  - a Scene tab for the same `sceneConfig` shape `ModelViewer` accepts (lighting, environment, background, post-processing, animations, annotations, and cinematic camera paths)
@@ -91,8 +98,8 @@ It also exports types for `ObjectBinding`, `ObjectBindingMaterial`, `ObjectBindi
91
98
 
92
99
  `SimpleModelViewer` provides:
93
100
 
94
- - a lightweight GLB/GLTF viewer that does not require `objectBindings` and does not require a `licenseKey`
95
- - optional local `.glb`, standalone/data-URI `.gltf`, or zipped GLTF upload mode for ad-hoc inspection without relying on the `modelUrl` asset
101
+ - a lightweight GLB/GLTF/OBJ/FBX/USDZ viewer that does not require `objectBindings` and does not require a `licenseKey`
102
+ - 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
96
103
  - built-in scene objects panel with search and visibility toggles
97
104
  - 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
98
105
  - click-to-select and fit-to-object focus behavior
@@ -112,7 +119,7 @@ domain model. It is a serializable record, keyed by mesh node name, that lets
112
119
  you:
113
120
 
114
121
  - declare which meshes are interactive
115
- - carry live render state such as visibility and per-object material overrides
122
+ - carry live render state such as visibility, local transforms, and per-object material overrides
116
123
  - attach actions like `change-color`, `change-material`, and `toggle-visibility`
117
124
  - store metadata and metrics alongside the 3D scene
118
125
  - save camera framing so object focus can use curated views
@@ -161,7 +168,16 @@ has its own meaning, state, or behavior," object bindings are a strong fit.
161
168
  npm install @liveroom-tech/react-immersive
162
169
  ```
163
170
 
164
- The component ships with its own internal UI styles through a CSS import. Tailwind is not required in the consuming app.
171
+ Import the library stylesheet once from your application root (for example,
172
+ Next.js `app/layout.tsx`):
173
+
174
+ ```tsx
175
+ import "@liveroom-tech/react-immersive/styles.css";
176
+ ```
177
+
178
+ The styles ship as a static CSS file rather than a runtime-injected `<style>`
179
+ tag, so a strict CSP does not need `style-src 'unsafe-inline'`. Tailwind is not
180
+ required in the consuming app.
165
181
 
166
182
  Peer dependencies:
167
183
 
@@ -174,7 +190,7 @@ Peer dependencies:
174
190
 
175
191
  - [Developer Portal](https://react-immersive.liveroom.dev), sign in, pick a plan, and generate a `licenseKey`
176
192
  - [Docs](https://react-immersive.liveroom.dev/docs), full reference for every component, hook, and prop
177
- - [Examples & community repo](https://github.com/liveroom-technologies/react-immersive), public docs source, starter examples, and community resources
193
+ - [Examples & community repo](https://github.com/liveroom-technologies/react-immersive-docs/examples), public docs source, starter examples, and community resources
178
194
  - [Report a bug / request a feature](https://github.com/liveroom-technologies/react-immersive-docs/issues), for public bug reports, docs issues, and feature requests
179
195
  - [Discussions & Q&A](https://github.com/liveroom-technologies/react-immersive-docs/discussions), ask questions, share ideas, and compare approaches
180
196
  - [Private support / security](mailto:developers@liveroom.xyz?subject=React%20Immersive%20Support), for license, account, confidential customer, or security-sensitive issues
@@ -185,7 +201,6 @@ Peer dependencies:
185
201
 
186
202
  ```tsx
187
203
  import {
188
- defineObjectBindings,
189
204
  ModelViewer,
190
205
  useObjectBinding,
191
206
  useObjectBindings,
@@ -196,6 +211,7 @@ import {
196
211
  useViewerModel,
197
212
  useViewerSelection,
198
213
  } from "@liveroom-tech/react-immersive";
214
+ import { defineObjectBindings } from "@liveroom-tech/react-immersive/utils";
199
215
 
200
216
  const initialBindings = defineObjectBindings({
201
217
  CarBody: {
@@ -412,7 +428,10 @@ updateMetadata({ group: "lights" }, { state: "on" });
412
428
 
413
429
  When a parent or store owns the state, use the pure `selectBindings` and
414
430
  `selectBindingKeys` helpers for the same queries, and `patchBindings` to update
415
- all matches.
431
+ all matches. Import these helpers from
432
+ `@liveroom-tech/react-immersive/utils` when the module must remain usable from a
433
+ React Server Component; the main entry continues to re-export them for
434
+ backwards compatibility.
416
435
 
417
436
  The hook returns:
418
437
 
@@ -624,21 +643,52 @@ export default function App() {
624
643
  }
625
644
  ```
626
645
 
627
- `BindingBuilder` accepts one required prop:
646
+ `BindingBuilder` accepts one required prop and the same optional decoder
647
+ configuration as the viewers:
628
648
 
629
649
  ```ts
630
650
  type BindingBuilderProps = {
631
651
  licenseKey: string;
652
+ licenseValidationUrl?: string;
653
+ dracoDecoderPath?: string | false;
654
+ ktx2TranscoderPath?: string | false;
655
+ meshopt?: boolean;
656
+ project?: BindingBuilderProject | null;
657
+ onProjectChange?: (snapshot: BindingBuilderProjectSnapshot, origin: "local" | "remote") => void;
658
+ projectChangeDebounceMs?: number;
659
+ onModelUpload?: (file: File) => Promise<BindingBuilderProjectModel>;
660
+ onEditLeaseChange?: (featureId: string, editing: boolean) => void;
632
661
  };
633
662
  ```
634
663
 
664
+ Pass `tilesetUrl` to `ModelViewer` for a processed 3D Tiles asset. The original
665
+ `modelUrl` remains the durable source/fallback URL, but is not downloaded by the
666
+ viewer while a tileset is present:
667
+
668
+ ```tsx
669
+ <ModelViewer
670
+ modelUrl="https://assets.example.com/source/building.glb"
671
+ tilesetUrl="https://assets.example.com/tiles/building/tileset.json"
672
+ tilesErrorTarget={8}
673
+ tilesCacheSize={800}
674
+ licenseKey="your-license-key"
675
+ objectBindings={{}}
676
+ />
677
+ ```
678
+
679
+ Tiles are a dynamic level-of-detail scene, so automatic mesh binding generation
680
+ is intentionally disabled in streamed `BindingBuilder` projects. Scene settings
681
+ remain editable. Per-part editing requires the conversion pipeline to provide a
682
+ stable CAD/object catalog that can be mapped to tile feature metadata.
683
+
635
684
  `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.
636
685
 
637
686
  Current behavior, Object tab:
638
687
 
639
- - load the bundled demo model or upload a `.glb`, standalone/data-URI `.gltf`, or `.zip` containing a `.gltf` plus its referenced `.bin` and texture files
688
+ - upload a `.glb`, standalone/data-URI `.gltf`, or `.zip` containing a `.gltf` plus its referenced `.bin` and texture files
640
689
  - traverse renderable mesh nodes and generate starter bindings automatically
641
690
  - edit binding identity, label, type, status, booleans, style, actions, metrics, metadata, and saved camera state
691
+ - 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
642
692
  - apply a one-click material preset (wood, metal, chrome, glass, plastic, fabric, ceramic, concrete, etc.) onto the selected object's material
643
693
  - undo/redo the current bindings (toolbar buttons or `Cmd/Ctrl+Z` / `Cmd/Ctrl+Shift+Z`); rapid edits coalesce into a single undo step
644
694
  - import a previously exported `objectBindings.json`, merged onto the current model by `modelObjectId`
@@ -654,6 +704,7 @@ Current behavior, Scene tab:
654
704
  Preview:
655
705
 
656
706
  - the selected node previews live in an embedded `ModelViewer`
707
+ - 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`
657
708
 
658
709
  ### `BindingBuilder` licensing & plan tiers
659
710
 
@@ -669,7 +720,7 @@ A locked feature renders in place (not hidden) with a message naming the require
669
720
 
670
721
  ## `SimpleModelViewer`
671
722
 
672
- `SimpleModelViewer` is an exported lightweight viewer for cases where you want to load a GLB/GLTF 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.
723
+ `SimpleModelViewer` is an exported lightweight viewer for cases where you want to load a GLB, GLTF, OBJ, FBX, or USDZ 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.
673
724
 
674
725
  Basic usage:
675
726
 
@@ -694,7 +745,7 @@ Sizing note:
694
745
 
695
746
  Current behavior:
696
747
 
697
- - discovers renderable meshes directly from the loaded GLB/GLTF scene
748
+ - discovers renderable meshes directly from the loaded model scene
698
749
  - renders a right-side scene objects panel with search and visibility toggles
699
750
  - renders a left-side environment/scene settings panel (background color, environment preset, auto-rotate, exposure, ambient + directional light) toggleable via `showSceneSettingsPanel`, with every control seedable via props
700
751
  - optionally replaces `modelUrl` with a drag-and-drop / file-picker flow when `enableModelUpload` is enabled
@@ -831,8 +882,11 @@ const { clips, currentClip, isPlaying, play, pause, stop, setSpeed } =
831
882
  The source code currently defines `ModelViewer` with these props:
832
883
 
833
884
  ```ts
885
+ type ObjectTransformSpace = "local" | "world";
886
+
834
887
  type Props = {
835
888
  modelUrl: string;
889
+ modelFormat?: "glb" | "gltf" | "obj" | "fbx" | "usdz";
836
890
  licenseKey: string;
837
891
  objectBindings: Record<string, ObjectBinding>;
838
892
  selectedObject?: ObjectBinding | null;
@@ -881,10 +935,15 @@ type Props = {
881
935
  sceneConfig?: SceneConfig;
882
936
  disableZoom?: boolean;
883
937
  zoomOnSelected?: boolean;
938
+ enableCameraControls?: boolean;
939
+ moveModeEnabled?: boolean;
940
+ objectTransformSpace?: ObjectTransformSpace;
941
+ enableKeyboardNavigation?: boolean;
884
942
  onAutoFit?: () => Promise<boolean>;
885
943
  refitOnResize?: boolean;
886
944
  renderMode?: "always" | "demand";
887
945
  maxDpr?: number;
946
+ preserveDrawingBuffer?: boolean;
888
947
  performanceProfile?: "auto" | "high" | "low";
889
948
  dracoDecoderPath?: string | false;
890
949
  ktx2TranscoderPath?: string | false;
@@ -900,12 +959,13 @@ type Props = {
900
959
  mobileHandoffUrl?: string;
901
960
  showAnnotationNavigation?: boolean;
902
961
  showAnnotationOnHover?: boolean;
962
+ showViewGizmo?: boolean;
903
963
  };
904
964
  ```
905
965
 
906
966
  ### `modelUrl`
907
967
 
908
- URL or public path to the `.glb` or `.gltf` model. For `.gltf` URLs with external `.bin` or texture files, those files must be hosted at the relative paths declared in the `.gltf`.
968
+ URL or public path to a `.glb`, `.gltf`, `.obj`, `.fbx`, or `.usdz` 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.
909
969
 
910
970
  Example:
911
971
 
@@ -913,6 +973,11 @@ Example:
913
973
  modelUrl = "/model.glb";
914
974
  ```
915
975
 
976
+ ### `modelFormat`
977
+
978
+ Optional explicit format for signed or extensionless model URLs. Normal URLs
979
+ ending in `.glb`, `.gltf`, `.obj`, `.fbx`, or `.usdz` are detected automatically.
980
+
916
981
  ### `licenseKey`
917
982
 
918
983
  License key for the library. Required.
@@ -932,7 +997,7 @@ A map keyed by the model node name. Each key should match a mesh name from the G
932
997
  Example:
933
998
 
934
999
  ```ts
935
- import { defineObjectBindings } from "@liveroom-tech/react-immersive";
1000
+ import { defineObjectBindings } from "@liveroom-tech/react-immersive/utils";
936
1001
 
937
1002
  const objectBindings = defineObjectBindings({
938
1003
  Object_2: {
@@ -1008,6 +1073,7 @@ This fires for:
1008
1073
  - color picks (`binding.style.material.baseColor`), committed when the color picker closes
1009
1074
  - texture uploads (`binding.style.material.texture.path`), committed immediately (via `onTextureUpload` when provided, otherwise as a blob URL)
1010
1075
  - texture removal (`binding.style.material.texture` cleared)
1076
+ - object movement and rotation (`binding.transform.position` and `binding.transform.rotation`)
1011
1077
 
1012
1078
  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.
1013
1079
 
@@ -1077,16 +1143,19 @@ type CaptureImageOptions = {
1077
1143
  };
1078
1144
  ```
1079
1145
 
1080
- `captureImage` 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.
1146
+ `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.
1081
1147
 
1082
1148
  ```tsx
1083
- onViewerReady={(viewer) => {
1084
- viewer
1085
- .captureImage({ width: 1920, height: 1080, transparent: true })
1086
- .then((dataUrl) => {
1087
- // upload, preview, etc.
1088
- });
1089
- }}
1149
+ <ModelViewer
1150
+ preserveDrawingBuffer
1151
+ onViewerReady={(viewer) => {
1152
+ viewer
1153
+ .captureImage({ width: 1920, height: 1080, transparent: true })
1154
+ .then((dataUrl) => {
1155
+ // upload, preview, etc.
1156
+ });
1157
+ }}
1158
+ />;
1090
1159
  ```
1091
1160
 
1092
1161
  Note: requesting a custom `width`/`height` briefly resizes the live canvas to render at that resolution before restoring it, which can cause a momentary flicker on screen, fine for an occasional snapshot, not for rapid/looped calls.
@@ -1098,7 +1167,10 @@ const camera = useViewerCamera();
1098
1167
  const effects = useViewerEffects();
1099
1168
  const connection = useViewerConnection(camera, effects);
1100
1169
 
1101
- <ModelViewer onViewerReady={connection.handleViewerReady} />;
1170
+ <ModelViewer
1171
+ preserveDrawingBuffer
1172
+ onViewerReady={connection.handleViewerReady}
1173
+ />;
1102
1174
 
1103
1175
  const dataUrl = await connection.captureImage({ width: 1920, height: 1080 });
1104
1176
  ```
@@ -1336,8 +1408,9 @@ true;
1336
1408
 
1337
1409
  ### `showDownloadButton`
1338
1410
 
1339
- Optional boolean that enables the download split-button (export GLB, export PNG
1340
- screenshot, or export the model's UV layout).
1411
+ Optional boolean that enables the download split-button (export GLB, export the
1412
+ model's UV layout, and—when `preserveDrawingBuffer` is enabled—export a PNG
1413
+ screenshot).
1341
1414
 
1342
1415
  Default:
1343
1416
 
@@ -1495,6 +1568,82 @@ Default:
1495
1568
  true;
1496
1569
  ```
1497
1570
 
1571
+ ### Moving and rotating objects
1572
+
1573
+ Set `moveModeEnabled` to show a centered Drei `PivotControls` gizmo for the
1574
+ selected bound object. Translation arrows, plane handles, and rotation rings
1575
+ are available together; scaling is not enabled. While object editing is on,
1576
+ the camera controls are paused so pointer drags manipulate the object instead
1577
+ of the camera.
1578
+
1579
+ ```tsx
1580
+ import { useState } from "react";
1581
+ import {
1582
+ ModelViewer,
1583
+ type ObjectBinding,
1584
+ } from "@liveroom-tech/react-immersive";
1585
+
1586
+ export function EditableViewer({
1587
+ initialBindings,
1588
+ }: {
1589
+ initialBindings: Record<string, ObjectBinding>;
1590
+ }) {
1591
+ const [objectBindings, setObjectBindings] = useState(initialBindings);
1592
+
1593
+ return (
1594
+ <ModelViewer
1595
+ modelUrl="/model.glb"
1596
+ licenseKey="your-license-key"
1597
+ objectBindings={objectBindings}
1598
+ onObjectBindingsChange={setObjectBindings}
1599
+ moveModeEnabled
1600
+ objectTransformSpace="world"
1601
+ />
1602
+ );
1603
+ }
1604
+ ```
1605
+
1606
+ Click a bound mesh to select it, then drag an arrow to move on one axis, a
1607
+ plane handle to move on two axes, or a ring to rotate. The pivot is placed at
1608
+ the center of the selected object's renderable geometry, so rotation happens
1609
+ around the object itself even when the source model's node origin is elsewhere.
1610
+
1611
+ `objectTransformSpace` accepts:
1612
+
1613
+ - `"world"` (default): the gizmo remains aligned with the scene axes
1614
+ - `"local"`: the gizmo follows the selected object's current orientation
1615
+
1616
+ Axis space changes the direction of the handles; it does not change the
1617
+ object's position when toggled. The resulting `transform.position` and
1618
+ `transform.rotation` values are always persisted in the object's local space,
1619
+ with rotation stored in radians.
1620
+
1621
+ Passing `onObjectBindingsChange` makes binding edits controlled, so the parent
1622
+ must store the returned record as shown above. Without that callback,
1623
+ `ModelViewer` keeps edits internally for the mounted viewer. Similarly, when
1624
+ you pass `selectedObject`, update it from `onObjectSelect`; otherwise selection
1625
+ is managed internally.
1626
+
1627
+ ### `enableCameraControls`
1628
+
1629
+ Optional boolean controlling orbit, pan, and zoom input. Default: `true`.
1630
+ Camera controls are temporarily disabled whenever `moveModeEnabled` is on.
1631
+
1632
+ ### `enableKeyboardNavigation`
1633
+
1634
+ Optional boolean enabling camera shortcuts after the viewer canvas is clicked
1635
+ or focused. Default: `false`. Use `W`/`S` to move forward/back, `A`/`D` to
1636
+ truck left/right, Space/`C` to move up/down, and the arrow keys to orbit.
1637
+ `Shift` + Up/Down moves forward/back. Keyboard navigation is inactive while
1638
+ object editing or camera controls are disabled.
1639
+
1640
+ ### `showViewGizmo`
1641
+
1642
+ Optional boolean showing a clickable orientation gizmo in the top-right of the
1643
+ viewer. Default: `false`. It indicates the camera's view of the scene axes and
1644
+ can snap the camera to an axis; it is separate from the selected object's
1645
+ transform gizmo and is hidden while `moveModeEnabled` is on.
1646
+
1498
1647
  ### `onAutoFit`
1499
1648
 
1500
1649
  Optional async callback fired once the model has loaded and the scene is ready. When provided, the viewer calls it so consumers can run their own fit-to-scene behavior (e.g. an animated fit); if omitted, the viewer falls back to its internal `fitScene`, unless an explicit [`camera`](#camera) prop was given, in which case that position is treated as the chosen initial view and `fitScene` is skipped.
@@ -1562,6 +1711,18 @@ Default:
1562
1711
  2;
1563
1712
  ```
1564
1713
 
1714
+ ### `preserveDrawingBuffer`
1715
+
1716
+ Optional boolean that retains the WebGL framebuffer for PNG screenshots. It is
1717
+ `false` by default to avoid imposing framebuffer-retention cost on every frame.
1718
+ Enable it when using `captureImage` or the built-in Download PNG action.
1719
+
1720
+ Default:
1721
+
1722
+ ```ts
1723
+ false;
1724
+ ```
1725
+
1565
1726
  ### `performanceProfile`
1566
1727
 
1567
1728
  Optional control over how aggressively the viewer trades visual fidelity for a stable WebGL context on constrained GPUs.
@@ -1590,7 +1751,43 @@ ktx2TranscoderPath?: string | false;
1590
1751
  meshopt?: boolean;
1591
1752
  ```
1592
1753
 
1593
- DRACO, Meshopt, and KTX2 decoding are enabled by default via hosted decoder/transcoder bundles, fetched lazily only when the model needs them. Pass a path to self-host the decoder/transcoder files, or `false` to disable that decoder.
1754
+ Meshopt is bundled and enabled by default. DRACO and KTX2 are also enabled by
1755
+ default through immutable, versioned decoder builds hosted on the first-party
1756
+ `assets.react-immersive.liveroom.dev` domain. They are fetched lazily only when a model
1757
+ uses the corresponding compression format.
1758
+
1759
+ ```ts
1760
+ dracoDecoderPath =
1761
+ "https://assets.react-immersive.liveroom.dev/three-r184/draco/";
1762
+ ktx2TranscoderPath =
1763
+ "https://assets.react-immersive.liveroom.dev/three-r184/basis/";
1764
+ ```
1765
+
1766
+ Pass `false` to disable either decoder. For a fully self-hosted or offline
1767
+ deployment, version-matched files are included under `dist/decoders`.
1768
+
1769
+ Copy those files into your app's public/static directory during deployment:
1770
+
1771
+ ```sh
1772
+ cp -R node_modules/@liveroom-tech/react-immersive/dist/decoders public/react-immersive-decoders
1773
+ ```
1774
+
1775
+ Then pass the public URL directories (including the trailing slash):
1776
+
1777
+ ```tsx
1778
+ <ModelViewer
1779
+ dracoDecoderPath="/react-immersive-decoders/draco/"
1780
+ ktx2TranscoderPath="/react-immersive-decoders/basis/"
1781
+ {...props}
1782
+ />
1783
+ ```
1784
+
1785
+ The same props are available on `SimpleModelViewer` and `BindingBuilder`. When
1786
+ using the built-in defaults with a strict CSP, allow the first-party asset
1787
+ origin and decoder workers; a typical baseline is
1788
+ `connect-src 'self' https://assets.react-immersive.liveroom.dev; worker-src 'self' blob:; script-src 'self' 'wasm-unsafe-eval'`.
1789
+ Self-hosted paths only require `'self'`. Older browsers may require their
1790
+ WebAssembly fallback policy as well.
1594
1791
 
1595
1792
  ### `showUvCheckerButton`
1596
1793
 
@@ -1795,6 +1992,10 @@ See the full variable list in the [`ModelViewer` docs](https://react-immersive.l
1795
1992
  ```ts
1796
1993
  type SimpleModelViewerProps = {
1797
1994
  modelUrl: string;
1995
+ modelFormat?: "glb" | "gltf" | "obj" | "fbx" | "usdz";
1996
+ dracoDecoderPath?: string | false;
1997
+ ktx2TranscoderPath?: string | false;
1998
+ meshopt?: boolean;
1798
1999
  backgroundColor?: string;
1799
2000
  showSceneObjectsPanel?: boolean;
1800
2001
  showSceneSettingsPanel?: boolean;
@@ -1821,7 +2022,12 @@ type SimpleModelViewerProps = {
1821
2022
 
1822
2023
  ### `modelUrl`
1823
2024
 
1824
- URL or public path to the `.glb` or `.gltf` model to inspect. For `.gltf` URLs with external `.bin` or texture files, those files must be hosted at the relative paths declared in the `.gltf`.
2025
+ URL or public path to the `.glb`, `.gltf`, `.obj`, `.fbx`, or `.usdz` 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.
2026
+
2027
+ ### `modelFormat`
2028
+
2029
+ Optional explicit format for signed or extensionless model URLs. Normal URLs
2030
+ ending in a supported extension are detected automatically.
1825
2031
 
1826
2032
  ### `backgroundColor`
1827
2033
 
@@ -1924,7 +2130,7 @@ true;
1924
2130
 
1925
2131
  ### `enableModelUpload`
1926
2132
 
1927
- Optional boolean that swaps the fixed `modelUrl` workflow for a built-in upload UI. When enabled, the viewer starts with a drag-and-drop / file-picker empty state and loads the uploaded `.glb`, standalone/data-URI `.gltf`, or `.zip` containing a `.gltf` plus its referenced `.bin` and texture files instead of the asset passed via `modelUrl`.
2133
+ 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.
1928
2134
 
1929
2135
  Default:
1930
2136
 
@@ -1997,7 +2203,9 @@ Notes:
1997
2203
  - the `objectBindings` record is keyed by node name, not by `id`
1998
2204
  - `group` and `tags` can be assigned in `BindingBuilder` and used as targets by binding helpers and runtime material or transform effects
1999
2205
  - `visible` is live render state, not just an initial default
2000
- - `transform` is persistent local-space render state; missing fields retain their model-authored values and removing it restores the authored transform
2206
+ - `transform` is persistent local-space render state; position values use model units and rotation values use radians
2207
+ - the transform gizmo is displayed at the center of the object's renderable geometry for intuitive rotation, even if the authored node origin is elsewhere; this does not rewrite the GLB/GLTF origin
2208
+ - missing transform fields retain their model-authored values, and removing `transform` restores the authored transform
2001
2209
  - `style.material.baseColor` is read by the renderer and is the committed source of truth for color picks
2002
2210
  - `style.material.texture.path` is read by the renderer and is the committed source of truth for texture uploads (stored as durable URLs via `onTextureUpload` when provided, or session-scoped blob URLs otherwise)
2003
2211
  - if no `texture.path` is provided and no `style.material.baseColor` is set, the viewer falls back to the node's original GLB/GLTF material
@@ -2062,11 +2270,13 @@ Because of that, it works best when mounted in a container with an explicit heig
2062
2270
 
2063
2271
  ## Usage Expectations For Models
2064
2272
 
2065
- To use this library successfully, your GLB/GLTF asset should follow these expectations:
2273
+ To use this library successfully, your model asset should follow these expectations:
2066
2274
 
2067
2275
  - the mesh node names must match the keys in `objectBindings` (or be referenced via `modelObjectId`)
2068
- - target nodes should have geometry and a `MeshStandardMaterial`
2069
- - the file should be accessible from the browser at `modelUrl`; hosted `.gltf` files must also serve external `.bin` and texture files at their declared relative paths
2276
+ - target nodes should have geometry and a supported Three.js mesh material; imported materials are normalized into the editable PBR pipeline
2277
+ - 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
2278
+ - OBJ does not contain animations; supported FBX and USDZ animation clips are exposed through the same animation controls as glTF
2279
+ - OBJ has no standard unit metadata, so set an appropriate measurement label/scene scale when real-world dimensions matter
2070
2280
 
2071
2281
  If a node name or material name does not match, that object will not render through the interactive binding flow.
2072
2282