storysplat-viewer 2.7.3 → 2.7.4

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
@@ -12,6 +12,7 @@ A powerful npm package for embedding and interacting with 3D Gaussian Splatting
12
12
  - [API Reference](#api-reference)
13
13
  - [Controlling the Viewer](#controlling-the-viewer)
14
14
  - [Configuration Options](#configuration-options)
15
+ - [Internationalization (i18n)](#internationalization-i18n)
15
16
  - [Events](#events)
16
17
  - [React Integration](#react-integration)
17
18
  - [Analytics & Tracking](#analytics--tracking)
@@ -99,6 +100,27 @@ const viewer = createViewer(
99
100
 
100
101
  > **Note:** When using `createViewer` with self-hosted scenes, views and bandwidth are not tracked. Use `createViewerFromSceneId` for StorySplat-hosted scenes to get analytics.
101
102
 
103
+ ### Scene Data Format
104
+
105
+ When using `createViewer()` with self-hosted JSON, the scene data should match the format exported by the StorySplat editor. Key fields:
106
+
107
+ | Field | Type | Description |
108
+ |-------|------|-------------|
109
+ | `loadedModelUrl` | `string` | URL to the .splat, .ply, or .ksplat file |
110
+ | `waypoints` | `array` | Camera path waypoints |
111
+ | `hotspots` | `array` | Interactive hotspot markers |
112
+ | `portals` | `array` | Scene-to-scene navigation portals |
113
+ | `activeSkyboxUrl` | `string` | URL to the skybox HDR/image file |
114
+ | `skyboxRotation` | `number` | Skybox rotation offset |
115
+ | `lights` | `array` | Scene lighting configuration |
116
+ | `particleSystems` | `array` | Particle effect systems |
117
+ | `customMeshes` | `array` | Imported 3D models |
118
+ | `uiColor` | `string` | Accent color for UI controls |
119
+ | `uiOptions` | `object` | UI visibility toggles and button labels |
120
+ | `defaultCameraMode` | `string` | Initial camera mode: `'tour'`, `'explore'`, or `'walk'` |
121
+
122
+ The viewer's transform layer normalizes all field variations automatically, so JSON exported from any version of the editor will work.
123
+
102
124
  ## API Reference
103
125
 
104
126
  ### createViewerFromSceneId
@@ -163,8 +185,10 @@ async function fetchSceneMeta(
163
185
  description: string;
164
186
  thumbnailUrl: string;
165
187
  userName: string;
188
+ userSlug: string;
166
189
  views: number;
167
190
  tags: string[];
191
+ category?: string;
168
192
  createdAt: string | null;
169
193
  }>
170
194
  ```
@@ -187,21 +211,49 @@ interface ViewerInstance {
187
211
  getWaypointCount: () => number;
188
212
 
189
213
  // Camera
214
+ setCameraMode: (mode: 'tour' | 'explore') => void;
215
+ getCameraMode: () => string;
216
+ setExploreMode: (mode: 'orbit' | 'fly') => void;
190
217
  setPosition: (x: number, y: number, z: number) => void;
191
218
  setRotation: (x: number, y: number, z: number) => void;
192
219
  getPosition: () => { x: number; y: number; z: number };
193
220
  getRotation: () => { x: number; y: number; z: number };
194
221
 
195
- // Playback
222
+ // Playback / Progress
196
223
  play: () => void;
197
224
  pause: () => void;
198
225
  stop: () => void;
199
226
  isPlaying: () => boolean;
227
+ setProgress: (progress: number) => void;
228
+ getProgress: () => number;
229
+
230
+ // Splat management
231
+ goToSplat: (url: string) => Promise<void>;
232
+ goToOriginalSplat: () => void;
233
+ getCurrentSplatUrl: () => string;
234
+ isShowingOriginalSplat: () => boolean;
235
+ getAdditionalSplats: () => Array<{ url: string; name?: string; waypointIndex: number; percentage: number }>;
236
+
237
+ // Audio
238
+ muteAll: () => void;
239
+ unmuteAll: () => void;
240
+ isMuted: () => boolean;
241
+
242
+ // Hotspots
243
+ getHotspots: () => Array<{ id: string; title?: string; type: string; position: { x: number; y: number; z: number } }>;
244
+ triggerHotspot: (id: string) => void;
245
+ closeHotspot: () => void;
246
+
247
+ // Portals
248
+ navigateToScene: (sceneId: string) => Promise<void>;
200
249
 
201
250
  // Lifecycle
202
251
  destroy: () => void;
203
252
  resize: () => void;
204
253
 
254
+ // UI customization
255
+ setButtonLabels: (labels: Partial<ButtonLabels>) => void;
256
+
205
257
  // Events
206
258
  on: (event: ViewerEvent, callback: (...args: any[]) => void) => void;
207
259
  off: (event: ViewerEvent, callback: (...args: any[]) => void) => void;
@@ -230,6 +282,7 @@ interface ViewerOptions {
230
282
  // Lazy loading
231
283
  lazyLoad?: boolean;
232
284
  lazyLoadThumbnail?: string;
285
+ lazyLoadThumbnailType?: 'image' | 'video' | 'gif'; // Thumbnail media type
233
286
  lazyLoadButtonText?: string;
234
287
  }
235
288
  ```
@@ -256,6 +309,90 @@ const viewer = createViewer(container, sceneData, {
256
309
  });
257
310
  ```
258
311
 
312
+ ## Internationalization (i18n)
313
+
314
+ All user-facing UI text can be customized via `ButtonLabels`. This enables full translation or label renaming.
315
+
316
+ ### At Creation Time
317
+
318
+ Set labels via `uiOptions.buttonLabels` in your scene data:
319
+
320
+ ```javascript
321
+ const sceneData = await fetch('/scene.json').then(r => r.json());
322
+
323
+ // Override labels before creating the viewer
324
+ sceneData.uiOptions = {
325
+ ...sceneData.uiOptions,
326
+ buttonLabels: {
327
+ tour: 'Recorrido',
328
+ explore: 'Explorar',
329
+ walk: 'Caminar',
330
+ next: 'Siguiente',
331
+ previous: 'Anterior',
332
+ waypoints: 'Puntos',
333
+ close: 'Cerrar',
334
+ loading: 'Cargando...',
335
+ }
336
+ };
337
+
338
+ const viewer = createViewer(container, sceneData);
339
+ ```
340
+
341
+ ### At Runtime
342
+
343
+ Update labels at any time with `setButtonLabels()`:
344
+
345
+ ```javascript
346
+ const viewer = await createViewerFromSceneId(container, sceneId);
347
+
348
+ // Switch to Spanish
349
+ viewer.setButtonLabels({
350
+ tour: 'Recorrido',
351
+ explore: 'Explorar',
352
+ walk: 'Caminar',
353
+ next: 'Siguiente',
354
+ previous: 'Anterior',
355
+ close: 'Cerrar',
356
+ loading: 'Cargando...',
357
+ });
358
+
359
+ // Only update specific labels (others keep their current values)
360
+ viewer.setButtonLabels({ next: 'Suivant', previous: 'Pr\u00e9c\u00e9dent' });
361
+ ```
362
+
363
+ ### Available Labels
364
+
365
+ | Key | Default | Description |
366
+ |-----|---------|-------------|
367
+ | `tour` | `"Tour"` | Tour mode button |
368
+ | `explore` | `"Explore"` | Explore mode button |
369
+ | `walk` | `"Walk"` | Walk mode button |
370
+ | `orbit` | `"Orbit"` | Orbit sub-mode button |
371
+ | `fly` | `"Fly"` | Fly sub-mode button |
372
+ | `next` | `"Next"` | Next waypoint button |
373
+ | `previous` | `"Prev"` | Previous waypoint button |
374
+ | `startExperience` | `"Start Experience"` | Start experience button |
375
+ | `fullscreen` | `"Fullscreen"` | Fullscreen button aria-label |
376
+ | `mute` / `unmute` | `"Mute"` / `"Unmute"` | Audio toggle |
377
+ | `waypoints` | `"Waypoints"` | Waypoint dropdown label |
378
+ | `close` | `"Close"` | Hotspot popup close button |
379
+ | `yes` / `cancel` | `"Yes"` / `"Cancel"` | Portal confirmation buttons |
380
+ | `switchScenes` | `"Switch scenes?"` | Portal default prompt |
381
+ | `hotspotDefaultTitle` | `"Hotspot"` | Fallback hotspot title |
382
+ | `openExternalLink` | `"Open External Link"` | Hotspot link button text |
383
+ | `vr` / `ar` | `"VR"` / `"AR"` | XR button labels |
384
+ | `exitVr` / `exitAr` | `"Exit VR"` / `"Exit AR"` | XR exit labels |
385
+ | `loading` | `"Loading..."` | Loading progress text |
386
+ | `loadingScene` | `"Loading {name}..."` | Portal scene loading (`{name}` replaced) |
387
+ | `helpTitle` | `"Controls & Help"` | Help panel title |
388
+ | `helpCameraModes` | `"Camera Modes:"` | Help section header |
389
+ | `helpTourDesc` | `"Follow predefined path"` | Help tour description |
390
+ | `helpExploreDesc` | `"Free movement"` | Help explore description |
391
+ | `helpWalkDesc` | `"First-person walking"` | Help walk description |
392
+ | `errorWebGLTitle` | `"Unable to Initialize 3D Graphics"` | WebGL error heading |
393
+ | `errorWebGLMessage` | `"Your browser or device..."` | WebGL error body |
394
+ | `percentageFormat` | `"{n}%"` | Progress percentage format |
395
+
259
396
  ## Events
260
397
 
261
398
  ```javascript
@@ -282,11 +419,39 @@ viewer.on('waypointChange', ({ index, waypoint }) => {
282
419
  // Playback state
283
420
  viewer.on('playbackStart', () => console.log('Playing'));
284
421
  viewer.on('playbackStop', () => console.log('Stopped'));
422
+ viewer.on('playbackComplete', () => console.log('Tour finished'));
423
+
424
+ // Camera mode
425
+ viewer.on('modeChange', ({ mode }) => {
426
+ console.log(`Camera mode: ${mode}`);
427
+ });
285
428
 
286
- // Errors
429
+ // Scroll progress
430
+ viewer.on('progressUpdate', ({ progress, index }) => {
431
+ console.log(`Progress: ${(progress * 100).toFixed(0)}%, waypoint ${index}`);
432
+ });
433
+
434
+ // XR sessions
435
+ viewer.on('xrStart', ({ type }) => console.log(`Entered ${type}`));
436
+ viewer.on('xrEnd', () => console.log('Exited XR'));
437
+
438
+ // Splat swap
439
+ viewer.on('splatChange', ({ url, isOriginal }) => {
440
+ console.log(`Splat changed: ${url} (original: ${isOriginal})`);
441
+ });
442
+
443
+ // Portal activated
444
+ viewer.on('portalActivated', ({ sceneId }) => {
445
+ console.log(`Navigating to scene: ${sceneId}`);
446
+ });
447
+
448
+ // Errors and warnings
287
449
  viewer.on('error', (error) => {
288
450
  console.error('Viewer error:', error);
289
451
  });
452
+ viewer.on('warning', ({ type, message }) => {
453
+ console.warn(`Warning [${type}]: ${message}`);
454
+ });
290
455
  ```
291
456
 
292
457
  ## React Integration
@@ -415,21 +580,48 @@ function goToWaypoint(index) {
415
580
 
416
581
  | Method | Description |
417
582
  |--------|-------------|
418
- | `viewer.play()` | Start auto-playing through waypoints |
419
- | `viewer.pause()` | Pause auto-play |
420
- | `viewer.stop()` | Stop and reset to first waypoint |
421
- | `viewer.isPlaying()` | Check if currently auto-playing |
583
+ | **Navigation** | |
584
+ | `viewer.goToWaypoint(index)` | Jump to specific waypoint (0-indexed) |
422
585
  | `viewer.nextWaypoint()` | Go to next waypoint |
423
586
  | `viewer.prevWaypoint()` | Go to previous waypoint |
424
- | `viewer.goToWaypoint(index)` | Jump to specific waypoint (0-indexed) |
425
587
  | `viewer.getCurrentWaypointIndex()` | Get current waypoint index |
426
588
  | `viewer.getWaypointCount()` | Get total number of waypoints |
589
+ | **Camera** | |
590
+ | `viewer.setCameraMode(mode)` | Switch mode: `'tour'` or `'explore'` |
591
+ | `viewer.getCameraMode()` | Get current camera mode |
592
+ | `viewer.setExploreMode(mode)` | Switch explore sub-mode: `'orbit'` or `'fly'` |
427
593
  | `viewer.setPosition(x, y, z)` | Set camera position |
428
594
  | `viewer.setRotation(x, y, z)` | Set camera rotation (Euler angles) |
429
595
  | `viewer.getPosition()` | Get current camera position |
430
596
  | `viewer.getRotation()` | Get current camera rotation |
597
+ | **Playback / Progress** | |
598
+ | `viewer.play()` | Start auto-playing through waypoints |
599
+ | `viewer.pause()` | Pause auto-play |
600
+ | `viewer.stop()` | Stop and reset to first waypoint |
601
+ | `viewer.isPlaying()` | Check if currently auto-playing |
602
+ | `viewer.setProgress(progress)` | Set scroll progress (0-1) |
603
+ | `viewer.getProgress()` | Get current scroll progress (0-1) |
604
+ | **Splat Management** | |
605
+ | `viewer.goToSplat(url)` | Swap to a different splat file at runtime |
606
+ | `viewer.goToOriginalSplat()` | Switch back to the original splat |
607
+ | `viewer.getCurrentSplatUrl()` | Get the currently loaded splat URL |
608
+ | `viewer.isShowingOriginalSplat()` | Check if showing the original splat |
609
+ | `viewer.getAdditionalSplats()` | Get the list of additional splat swap points |
610
+ | **Audio** | |
611
+ | `viewer.muteAll()` | Mute all audio |
612
+ | `viewer.unmuteAll()` | Unmute all audio |
613
+ | `viewer.isMuted()` | Check if audio is muted |
614
+ | **Hotspots** | |
615
+ | `viewer.getHotspots()` | Get all hotspot data (id, title, type, position) |
616
+ | `viewer.triggerHotspot(id)` | Programmatically open a hotspot popup |
617
+ | `viewer.closeHotspot()` | Close the currently open hotspot popup |
618
+ | **Portals** | |
619
+ | `viewer.navigateToScene(sceneId)` | Navigate to a linked scene via portal |
620
+ | **Lifecycle** | |
431
621
  | `viewer.resize()` | Recalculate canvas size (call after container resize) |
432
622
  | `viewer.destroy()` | Clean up and remove viewer |
623
+ | **UI** | |
624
+ | `viewer.setButtonLabels(labels)` | Update UI text labels at runtime (see [i18n](#internationalization-i18n)) |
433
625
 
434
626
  ## Analytics & Tracking
435
627
 
@@ -654,7 +846,7 @@ import { generateHTML, generateHTMLFromUrl } from 'storysplat-viewer';
654
846
  // From scene data object
655
847
  const html = generateHTML(sceneData, {
656
848
  title: 'My Scene',
657
- description: 'An interactive 3D experience'
849
+ description: 'An interactive 3D experience',
658
850
  });
659
851
 
660
852
  // From scene JSON URL
@@ -668,6 +860,21 @@ import fs from 'fs';
668
860
  fs.writeFileSync('scene.html', html);
669
861
  ```
670
862
 
863
+ #### GenerateHTMLOptions
864
+
865
+ ```typescript
866
+ interface GenerateHTMLOptions {
867
+ cdnUrl?: string; // Custom CDN URL for the viewer bundle (default: unpkg)
868
+ title?: string; // HTML page title (default: scene name)
869
+ description?: string; // Meta description for SEO
870
+ faviconUrl?: string; // Favicon URL
871
+ customCSS?: string; // Custom CSS to inject into the page
872
+ minify?: boolean; // Minify the output HTML
873
+ lazyLoad?: boolean; // Show thumbnail + start button before loading
874
+ lazyLoadButtonText?: string; // Custom text for the lazy load button
875
+ }
876
+ ```
877
+
671
878
  ## Portals (Scene-to-Scene Navigation)
672
879
 
673
880
  Portals allow users to navigate between multiple 3D scenes by clicking or walking near portal markers.