storysplat-viewer 0.1.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 +659 -0
- package/dist/esm/dist/styles/viewer.css +2 -0
- package/dist/esm/dist/styles/viewer.css.map +1 -0
- package/dist/esm/index.js +187107 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/types/core/cameraManager.d.ts +35 -0
- package/dist/types/core/lightingManager.d.ts +9 -0
- package/dist/types/core/renderLoopManager.d.ts +24 -0
- package/dist/types/core/sceneManager.d.ts +19 -0
- package/dist/types/core/splatLoader.d.ts +12 -0
- package/dist/types/features/analyticsManager.d.ts +102 -0
- package/dist/types/features/animatedGifTexture.d.ts +37 -0
- package/dist/types/features/audioManager.d.ts +72 -0
- package/dist/types/features/collisionManager.d.ts +51 -0
- package/dist/types/features/customMeshManager.d.ts +15 -0
- package/dist/types/features/hotspotManager.d.ts +16 -0
- package/dist/types/features/navigationManager.d.ts +142 -0
- package/dist/types/features/particleManager.d.ts +14 -0
- package/dist/types/features/sceneFeatures.d.ts +14 -0
- package/dist/types/features/skyboxManager.d.ts +8 -0
- package/dist/types/features/splatSwapManager.d.ts +87 -0
- package/dist/types/features/xrManager.d.ts +12 -0
- package/dist/types/index.d.ts +55 -0
- package/dist/types/types/dataTypes.d.ts +248 -0
- package/dist/types/types/hotspotTypes.d.ts +82 -0
- package/dist/types/types/index.d.ts +6 -0
- package/dist/types/types/interactionTypes.d.ts +43 -0
- package/dist/types/types/navigationTypes.d.ts +41 -0
- package/dist/types/types/sceneFeatures.d.ts +186 -0
- package/dist/types/types/viewerOptions.d.ts +245 -0
- package/dist/types/types.d.ts +106 -0
- package/dist/types/ui/helpPanel.d.ts +32 -0
- package/dist/types/ui/muteButton.d.ts +30 -0
- package/dist/types/ui/preloader.d.ts +40 -0
- package/dist/types/ui/startButton.d.ts +29 -0
- package/dist/types/ui/uiManager.d.ts +97 -0
- package/dist/types/ui/watermark.d.ts +18 -0
- package/dist/types/utils/helpers.d.ts +81 -0
- package/dist/types/viewer-simple.d.ts +30 -0
- package/dist/types/viewer.d.ts +105 -0
- package/package.json +47 -0
package/README.md
ADDED
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
# StorySplat Viewer
|
|
2
|
+
|
|
3
|
+
A powerful npm package for embedding and interacting with 3D Gaussian Splatting scenes in web applications. StorySplat Viewer provides a complete solution for displaying, navigating, and interacting with .splat files with built-in support for waypoints, hotspots, and multiple camera modes.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [Overview](#overview)
|
|
8
|
+
- [Installation](#installation)
|
|
9
|
+
- [Quick Start](#quick-start)
|
|
10
|
+
- [API Reference](#api-reference)
|
|
11
|
+
- [Configuration Options](#configuration-options)
|
|
12
|
+
- [Examples](#examples)
|
|
13
|
+
- [Events and Callbacks](#events-and-callbacks)
|
|
14
|
+
- [Styling](#styling)
|
|
15
|
+
- [Migration Guide](#migration-guide)
|
|
16
|
+
- [Troubleshooting](#troubleshooting)
|
|
17
|
+
|
|
18
|
+
## Overview
|
|
19
|
+
|
|
20
|
+
StorySplat Viewer is a TypeScript/JavaScript library that enables seamless integration of 3D Gaussian Splatting scenes into web applications. It provides:
|
|
21
|
+
|
|
22
|
+
- 🎥 Multiple camera modes (orbit, free, first-person)
|
|
23
|
+
- 🎯 Interactive hotspots with rich content
|
|
24
|
+
- 🚶 Waypoint-based navigation system
|
|
25
|
+
- 📱 Mobile-friendly touch controls
|
|
26
|
+
- 🎨 Customizable UI and styling
|
|
27
|
+
- 🔧 Comprehensive API for programmatic control
|
|
28
|
+
- âš¡ Optimized performance with BabylonJS engine
|
|
29
|
+
|
|
30
|
+
## Installation
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @storysplat/viewer
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Or using yarn:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
yarn add @storysplat/viewer
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
```javascript
|
|
45
|
+
import { initializeViewer } from '@storysplat/viewer';
|
|
46
|
+
import '@storysplat/viewer/dist/styles/viewer.css';
|
|
47
|
+
|
|
48
|
+
// Initialize the viewer
|
|
49
|
+
const viewerElement = document.getElementById('storysplat-viewer');
|
|
50
|
+
const viewer = initializeViewer(viewerElement, {
|
|
51
|
+
splatUrl: 'https://example.com/scene.splat',
|
|
52
|
+
targetFps: 60,
|
|
53
|
+
fov: 60,
|
|
54
|
+
cameraMode: 'orbit'
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Load additional splats
|
|
58
|
+
await viewer.loadSplat('https://example.com/another.splat');
|
|
59
|
+
|
|
60
|
+
// Switch camera mode
|
|
61
|
+
viewer.setCameraMode('free');
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## API Reference
|
|
65
|
+
|
|
66
|
+
### initializeViewer
|
|
67
|
+
|
|
68
|
+
Creates and initializes a new StorySplat viewer instance.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
function initializeViewer(
|
|
72
|
+
element: HTMLElement,
|
|
73
|
+
data: StorySplatData,
|
|
74
|
+
options?: Partial<ViewerOptions>
|
|
75
|
+
): ViewerInstance
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Parameters:**
|
|
79
|
+
- `element` (HTMLElement): The DOM element to render the viewer into
|
|
80
|
+
- `data` (StorySplatData): Scene data including splat URL and configuration
|
|
81
|
+
- `options` (Partial<ViewerOptions>): Optional viewer configuration
|
|
82
|
+
|
|
83
|
+
**Returns:** ViewerInstance object for controlling the viewer
|
|
84
|
+
|
|
85
|
+
### ViewerInstance Interface
|
|
86
|
+
|
|
87
|
+
The main interface for interacting with the viewer after initialization.
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
interface ViewerInstance {
|
|
91
|
+
// Scene Management
|
|
92
|
+
loadSplat(url: string, options?: LoadOptions): Promise<void>;
|
|
93
|
+
clearScene(): void;
|
|
94
|
+
destroy(): void;
|
|
95
|
+
|
|
96
|
+
// Camera Controls
|
|
97
|
+
setCameraMode(mode: 'orbit' | 'free' | 'firstPerson'): void;
|
|
98
|
+
getCameraMode(): string;
|
|
99
|
+
setCameraPosition(x: number, y: number, z: number): void;
|
|
100
|
+
getCameraPosition(): { x: number; y: number; z: number };
|
|
101
|
+
setCameraTarget(x: number, y: number, z: number): void;
|
|
102
|
+
getCameraTarget(): { x: number; y: number; z: number };
|
|
103
|
+
|
|
104
|
+
// Navigation
|
|
105
|
+
navigateToWaypoint(waypointId: string): Promise<void>;
|
|
106
|
+
getWaypoints(): Waypoint[];
|
|
107
|
+
startWaypointTour(waypointIds?: string[]): void;
|
|
108
|
+
stopWaypointTour(): void;
|
|
109
|
+
|
|
110
|
+
// Hotspots
|
|
111
|
+
getHotspots(): Hotspot[];
|
|
112
|
+
showHotspot(hotspotId: string): void;
|
|
113
|
+
hideHotspot(hotspotId: string): void;
|
|
114
|
+
updateHotspot(hotspotId: string, data: Partial<Hotspot>): void;
|
|
115
|
+
|
|
116
|
+
// Rendering
|
|
117
|
+
setRenderingEnabled(enabled: boolean): void;
|
|
118
|
+
takeScreenshot(options?: ScreenshotOptions): Promise<Blob>;
|
|
119
|
+
|
|
120
|
+
// Scene Properties
|
|
121
|
+
setBackgroundColor(color: string): void;
|
|
122
|
+
setSkybox(skyboxUrl: string): void;
|
|
123
|
+
setFog(enabled: boolean, density?: number): void;
|
|
124
|
+
|
|
125
|
+
// Events
|
|
126
|
+
on(event: string, callback: Function): void;
|
|
127
|
+
off(event: string, callback: Function): void;
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Data Types
|
|
132
|
+
|
|
133
|
+
#### StorySplatData
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
interface StorySplatData {
|
|
137
|
+
splatUrl: string;
|
|
138
|
+
waypoints?: Waypoint[];
|
|
139
|
+
hotspots?: Hotspot[];
|
|
140
|
+
cameraSettings?: CameraSettings;
|
|
141
|
+
environmentSettings?: EnvironmentSettings;
|
|
142
|
+
meshes?: CustomMesh[];
|
|
143
|
+
particleSystems?: ParticleSystemConfig[];
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### ViewerOptions
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
interface ViewerOptions {
|
|
151
|
+
// Camera settings
|
|
152
|
+
cameraMode?: 'orbit' | 'free' | 'firstPerson';
|
|
153
|
+
fov?: number;
|
|
154
|
+
initialCameraPosition?: { x: number; y: number; z: number };
|
|
155
|
+
initialCameraTarget?: { x: number; y: number; z: number };
|
|
156
|
+
|
|
157
|
+
// Rendering
|
|
158
|
+
targetFps?: number;
|
|
159
|
+
backgroundColor?: string;
|
|
160
|
+
skyboxUrl?: string;
|
|
161
|
+
|
|
162
|
+
// UI Options
|
|
163
|
+
showToolbar?: boolean;
|
|
164
|
+
showMobileControls?: boolean;
|
|
165
|
+
showLoadingIndicator?: boolean;
|
|
166
|
+
watermarkText?: string;
|
|
167
|
+
|
|
168
|
+
// Interaction
|
|
169
|
+
enableHotspots?: boolean;
|
|
170
|
+
enableWaypoints?: boolean;
|
|
171
|
+
enableCollisions?: boolean;
|
|
172
|
+
|
|
173
|
+
// Callbacks
|
|
174
|
+
onReady?: () => void;
|
|
175
|
+
onError?: (error: Error) => void;
|
|
176
|
+
onProgress?: (progress: number) => void;
|
|
177
|
+
onHotspotClick?: (hotspot: Hotspot) => void;
|
|
178
|
+
onWaypointReached?: (waypoint: Waypoint) => void;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Waypoint
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
interface Waypoint {
|
|
186
|
+
id: string;
|
|
187
|
+
name: string;
|
|
188
|
+
position: { x: number; y: number; z: number };
|
|
189
|
+
lookAt?: { x: number; y: number; z: number };
|
|
190
|
+
duration?: number; // Animation duration in ms
|
|
191
|
+
easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out';
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### Hotspot
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
interface Hotspot {
|
|
199
|
+
id: string;
|
|
200
|
+
position: { x: number; y: number; z: number };
|
|
201
|
+
type: 'info' | 'link' | 'media' | 'custom';
|
|
202
|
+
title?: string;
|
|
203
|
+
content?: string;
|
|
204
|
+
mediaUrl?: string;
|
|
205
|
+
linkUrl?: string;
|
|
206
|
+
icon?: string;
|
|
207
|
+
color?: string;
|
|
208
|
+
size?: number;
|
|
209
|
+
alwaysVisible?: boolean;
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Configuration Options
|
|
214
|
+
|
|
215
|
+
### Camera Settings
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
const viewer = initializeViewer(element, {
|
|
219
|
+
splatUrl: 'scene.splat'
|
|
220
|
+
}, {
|
|
221
|
+
cameraMode: 'orbit', // 'orbit' | 'free' | 'firstPerson'
|
|
222
|
+
fov: 60, // Field of view in degrees
|
|
223
|
+
initialCameraPosition: { x: 0, y: 5, z: 10 },
|
|
224
|
+
initialCameraTarget: { x: 0, y: 0, z: 0 }
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Rendering Options
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
const viewer = initializeViewer(element, data, {
|
|
232
|
+
targetFps: 60, // Target frame rate
|
|
233
|
+
backgroundColor: '#000000', // Background color
|
|
234
|
+
skyboxUrl: '/assets/skybox.dds', // Optional skybox
|
|
235
|
+
showLoadingIndicator: true // Show loading progress
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### UI Options
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
const viewer = initializeViewer(element, data, {
|
|
243
|
+
showToolbar: true, // Show control toolbar
|
|
244
|
+
showMobileControls: true, // Auto-detect and show mobile controls
|
|
245
|
+
watermarkText: 'My Scene' // Custom watermark
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
## Examples
|
|
250
|
+
|
|
251
|
+
### Basic Setup
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
import { initializeViewer } from '@storysplat/viewer';
|
|
255
|
+
import '@storysplat/viewer/dist/styles/viewer.css';
|
|
256
|
+
|
|
257
|
+
function setupViewer() {
|
|
258
|
+
const element = document.getElementById('storysplat-viewer');
|
|
259
|
+
|
|
260
|
+
const viewer = initializeViewer(element, {
|
|
261
|
+
splatUrl: '/models/scene.splat'
|
|
262
|
+
}, {
|
|
263
|
+
onReady: () => console.log('Viewer ready!'),
|
|
264
|
+
onError: (error) => console.error('Viewer error:', error)
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
return viewer;
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Camera Mode Switching
|
|
272
|
+
|
|
273
|
+
```javascript
|
|
274
|
+
// Create camera mode buttons
|
|
275
|
+
const viewer = initializeViewer(element, { splatUrl: 'scene.splat' });
|
|
276
|
+
|
|
277
|
+
document.getElementById('orbit-btn').onclick = () => {
|
|
278
|
+
viewer.setCameraMode('orbit');
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
document.getElementById('free-btn').onclick = () => {
|
|
282
|
+
viewer.setCameraMode('free');
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
document.getElementById('fps-btn').onclick = () => {
|
|
286
|
+
viewer.setCameraMode('firstPerson');
|
|
287
|
+
};
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### Waypoint Navigation
|
|
291
|
+
|
|
292
|
+
```javascript
|
|
293
|
+
const viewer = initializeViewer(element, {
|
|
294
|
+
splatUrl: '/models/scene.splat',
|
|
295
|
+
waypoints: [
|
|
296
|
+
{ id: 'entrance', name: 'Entrance', position: { x: 0, y: 0, z: 5 } },
|
|
297
|
+
{ id: 'center', name: 'Center', position: { x: 0, y: 0, z: 0 } },
|
|
298
|
+
{ id: 'exit', name: 'Exit', position: { x: 0, y: 0, z: -5 } }
|
|
299
|
+
]
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// Navigate to specific waypoint
|
|
303
|
+
await viewer.navigateToWaypoint('center');
|
|
304
|
+
|
|
305
|
+
// Start automated tour
|
|
306
|
+
viewer.startWaypointTour(['entrance', 'center', 'exit']);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Hotspot Interaction
|
|
310
|
+
|
|
311
|
+
```javascript
|
|
312
|
+
const viewer = initializeViewer(element, {
|
|
313
|
+
splatUrl: 'scene.splat',
|
|
314
|
+
hotspots: [
|
|
315
|
+
{
|
|
316
|
+
id: 'info-1',
|
|
317
|
+
position: { x: 2, y: 1, z: 0 },
|
|
318
|
+
type: 'info',
|
|
319
|
+
title: 'Information',
|
|
320
|
+
content: 'This is an information hotspot'
|
|
321
|
+
}
|
|
322
|
+
]
|
|
323
|
+
}, {
|
|
324
|
+
onHotspotClick: (hotspot) => {
|
|
325
|
+
console.log('Hotspot clicked:', hotspot);
|
|
326
|
+
// Custom hotspot handling
|
|
327
|
+
if (hotspot.type === 'link') {
|
|
328
|
+
window.open(hotspot.linkUrl, '_blank');
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Add custom hotspot dynamically
|
|
334
|
+
viewer.updateHotspot('custom-1', {
|
|
335
|
+
position: { x: -2, y: 1, z: 0 },
|
|
336
|
+
type: 'info',
|
|
337
|
+
title: 'Dynamic Hotspot',
|
|
338
|
+
content: 'Added at runtime',
|
|
339
|
+
color: '#ff0000'
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Dynamic Splat Loading
|
|
344
|
+
|
|
345
|
+
```javascript
|
|
346
|
+
const viewer = initializeViewer(element, {
|
|
347
|
+
splatUrl: '/models/base.splat'
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Load additional splats
|
|
351
|
+
async function loadScenes() {
|
|
352
|
+
await viewer.loadSplat('/models/building.splat');
|
|
353
|
+
await viewer.loadSplat('/models/furniture.splat', {
|
|
354
|
+
position: { x: 0, y: 0, z: 0 },
|
|
355
|
+
scale: 1.5,
|
|
356
|
+
rotation: { x: 0, y: 90, z: 0 }
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Clear and load new scene
|
|
361
|
+
async function switchScene() {
|
|
362
|
+
viewer.clearScene();
|
|
363
|
+
await viewer.loadSplat('/models/new-scene.splat');
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
### Custom UI Integration
|
|
368
|
+
|
|
369
|
+
```javascript
|
|
370
|
+
const viewer = initializeViewer(element, data, {
|
|
371
|
+
showToolbar: false // Hide default toolbar
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
// Create custom controls
|
|
375
|
+
const customUI = {
|
|
376
|
+
updateCameraInfo() {
|
|
377
|
+
const pos = viewer.getCameraPosition();
|
|
378
|
+
document.getElementById('camera-info').textContent =
|
|
379
|
+
`Camera: ${pos.x.toFixed(2)}, ${pos.y.toFixed(2)}, ${pos.z.toFixed(2)}`;
|
|
380
|
+
},
|
|
381
|
+
|
|
382
|
+
takePhoto() {
|
|
383
|
+
viewer.takeScreenshot({ width: 1920, height: 1080 })
|
|
384
|
+
.then(blob => {
|
|
385
|
+
const url = URL.createObjectURL(blob);
|
|
386
|
+
window.open(url, '_blank');
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// Update camera info on render
|
|
392
|
+
viewer.on('render', customUI.updateCameraInfo);
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Events and Callbacks
|
|
396
|
+
|
|
397
|
+
### Available Events
|
|
398
|
+
|
|
399
|
+
```javascript
|
|
400
|
+
viewer.on('ready', () => {
|
|
401
|
+
console.log('Viewer initialized and ready');
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
viewer.on('loadStart', (url) => {
|
|
405
|
+
console.log('Started loading:', url);
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
viewer.on('loadProgress', (progress) => {
|
|
409
|
+
console.log('Loading progress:', progress + '%');
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
viewer.on('loadComplete', () => {
|
|
413
|
+
console.log('Loading complete');
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
viewer.on('error', (error) => {
|
|
417
|
+
console.error('Viewer error:', error);
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
viewer.on('cameraMove', (position) => {
|
|
421
|
+
console.log('Camera moved to:', position);
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
viewer.on('hotspotClick', (hotspot) => {
|
|
425
|
+
console.log('Hotspot clicked:', hotspot);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
viewer.on('waypointReached', (waypoint) => {
|
|
429
|
+
console.log('Reached waypoint:', waypoint);
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
viewer.on('render', (deltaTime) => {
|
|
433
|
+
// Called every frame
|
|
434
|
+
});
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Removing Event Listeners
|
|
438
|
+
|
|
439
|
+
```javascript
|
|
440
|
+
const handler = (position) => console.log(position);
|
|
441
|
+
viewer.on('cameraMove', handler);
|
|
442
|
+
|
|
443
|
+
// Later...
|
|
444
|
+
viewer.off('cameraMove', handler);
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
## Styling
|
|
448
|
+
|
|
449
|
+
### CSS Variables
|
|
450
|
+
|
|
451
|
+
The viewer uses CSS variables for theming:
|
|
452
|
+
|
|
453
|
+
```css
|
|
454
|
+
#storysplat-viewer {
|
|
455
|
+
--storysplat-primary-color: #007bff;
|
|
456
|
+
--storysplat-background-color: #000000;
|
|
457
|
+
--storysplat-text-color: #ffffff;
|
|
458
|
+
--storysplat-toolbar-bg: rgba(0, 0, 0, 0.8);
|
|
459
|
+
--storysplat-button-hover: #0056b3;
|
|
460
|
+
--storysplat-hotspot-color: #ffc107;
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Custom Styling
|
|
465
|
+
|
|
466
|
+
```css
|
|
467
|
+
/* Override toolbar styles */
|
|
468
|
+
.storysplat-toolbar {
|
|
469
|
+
background: linear-gradient(to bottom, rgba(0,0,0,0.9), rgba(0,0,0,0.7));
|
|
470
|
+
border-radius: 8px;
|
|
471
|
+
margin: 10px;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/* Custom hotspot appearance */
|
|
475
|
+
.storysplat-hotspot {
|
|
476
|
+
animation: pulse 2s infinite;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
@keyframes pulse {
|
|
480
|
+
0% { transform: scale(1); }
|
|
481
|
+
50% { transform: scale(1.1); }
|
|
482
|
+
100% { transform: scale(1); }
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/* Mobile controls */
|
|
486
|
+
.storysplat-mobile-controls {
|
|
487
|
+
opacity: 0.8;
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
### Required CSS
|
|
492
|
+
|
|
493
|
+
Make sure to import the viewer CSS:
|
|
494
|
+
|
|
495
|
+
```javascript
|
|
496
|
+
import '@storysplat/viewer/dist/styles/viewer.css';
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Or include it in your HTML:
|
|
500
|
+
|
|
501
|
+
```html
|
|
502
|
+
<link rel="stylesheet" href="node_modules/@storysplat/viewer/dist/styles/viewer.css">
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
## Migration Guide
|
|
506
|
+
|
|
507
|
+
### From Exported HTML to NPM Package
|
|
508
|
+
|
|
509
|
+
If you're migrating from StorySplat's exported HTML files to the npm package:
|
|
510
|
+
|
|
511
|
+
1. **Extract Scene Data**
|
|
512
|
+
```javascript
|
|
513
|
+
// From your exported HTML's scene configuration
|
|
514
|
+
const sceneData = {
|
|
515
|
+
splatUrl: 'path/to/your.splat',
|
|
516
|
+
waypoints: [...], // Copy waypoints array
|
|
517
|
+
hotspots: [...], // Copy hotspots array
|
|
518
|
+
cameraSettings: {
|
|
519
|
+
position: { x: 0, y: 5, z: 10 },
|
|
520
|
+
target: { x: 0, y: 0, z: 0 }
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
2. **Initialize Viewer**
|
|
526
|
+
```javascript
|
|
527
|
+
const element = document.getElementById('viewer-container');
|
|
528
|
+
const viewer = initializeViewer(element, sceneData, {
|
|
529
|
+
// Match your export settings
|
|
530
|
+
backgroundColor: '#000000',
|
|
531
|
+
targetFps: 60,
|
|
532
|
+
cameraMode: 'orbit'
|
|
533
|
+
});
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
3. **Update Event Handlers**
|
|
537
|
+
```javascript
|
|
538
|
+
// Old: onclick handlers in HTML
|
|
539
|
+
// New: Event listeners
|
|
540
|
+
viewer.on('hotspotClick', handleHotspotClick);
|
|
541
|
+
viewer.on('waypointReached', handleWaypointReached);
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
4. **Handle Assets**
|
|
545
|
+
```javascript
|
|
546
|
+
// Ensure all assets use absolute URLs or are properly bundled
|
|
547
|
+
const sceneData = {
|
|
548
|
+
splatUrl: '/assets/scene.splat',
|
|
549
|
+
skyboxUrl: '/assets/skybox.dds'
|
|
550
|
+
};
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
## Troubleshooting
|
|
554
|
+
|
|
555
|
+
### Common Issues
|
|
556
|
+
|
|
557
|
+
#### Viewer Not Rendering
|
|
558
|
+
|
|
559
|
+
```javascript
|
|
560
|
+
// Check container exists and has dimensions
|
|
561
|
+
const container = document.getElementById('storysplat-viewer');
|
|
562
|
+
if (!container) {
|
|
563
|
+
console.error('Container element not found');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Ensure container has explicit dimensions
|
|
567
|
+
#storysplat-viewer {
|
|
568
|
+
width: 100%;
|
|
569
|
+
height: 500px; /* Must have explicit height */
|
|
570
|
+
}
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
#### CORS Errors
|
|
574
|
+
|
|
575
|
+
```javascript
|
|
576
|
+
// For cross-origin splat files, ensure proper CORS headers
|
|
577
|
+
// Or use a proxy:
|
|
578
|
+
const viewer = initializeViewer(element, {
|
|
579
|
+
splatUrl: '/api/proxy?url=' + encodeURIComponent(splatUrl)
|
|
580
|
+
});
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
#### Performance Issues
|
|
584
|
+
|
|
585
|
+
```javascript
|
|
586
|
+
// Reduce target FPS for better performance
|
|
587
|
+
const viewer = initializeViewer(element, data, {
|
|
588
|
+
targetFps: 30, // Lower FPS
|
|
589
|
+
// Disable expensive features
|
|
590
|
+
skyboxUrl: null,
|
|
591
|
+
showLoadingIndicator: false
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// Optimize rendering
|
|
595
|
+
viewer.setRenderingEnabled(false); // Pause when not visible
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
#### Mobile Touch Controls Not Working
|
|
599
|
+
|
|
600
|
+
```javascript
|
|
601
|
+
// Ensure mobile controls are enabled
|
|
602
|
+
const viewer = initializeViewer(element, data, {
|
|
603
|
+
showMobileControls: true,
|
|
604
|
+
// Force mobile controls for testing
|
|
605
|
+
forceMobileControls: true
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
// Check viewport meta tag
|
|
609
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
#### Memory Leaks
|
|
613
|
+
|
|
614
|
+
```javascript
|
|
615
|
+
// Always cleanup when done
|
|
616
|
+
window.addEventListener('beforeunload', () => {
|
|
617
|
+
viewer.destroy();
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
// In React/Vue/Angular components
|
|
621
|
+
componentWillUnmount() {
|
|
622
|
+
if (this.viewer) {
|
|
623
|
+
this.viewer.destroy();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
### Debug Mode
|
|
629
|
+
|
|
630
|
+
Enable debug logging:
|
|
631
|
+
|
|
632
|
+
```javascript
|
|
633
|
+
const viewer = initializeViewer(element, data, {
|
|
634
|
+
debug: true, // Enable debug logging
|
|
635
|
+
onDebug: (message) => console.log('[Debug]', message)
|
|
636
|
+
});
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
### Browser Compatibility
|
|
640
|
+
|
|
641
|
+
StorySplat Viewer requires:
|
|
642
|
+
- WebGL 2.0 support
|
|
643
|
+
- Modern JavaScript (ES2015+)
|
|
644
|
+
- Recommended browsers:
|
|
645
|
+
- Chrome 80+
|
|
646
|
+
- Firefox 75+
|
|
647
|
+
- Safari 13+
|
|
648
|
+
- Edge 80+
|
|
649
|
+
|
|
650
|
+
### Support
|
|
651
|
+
|
|
652
|
+
For issues, feature requests, or questions:
|
|
653
|
+
- GitHub Issues: [github.com/storysplat/viewer/issues](https://github.com/storysplat/viewer/issues)
|
|
654
|
+
- Documentation: [docs.storysplat.com](https://docs.storysplat.com)
|
|
655
|
+
- Discord: [discord.gg/storysplat](https://discord.gg/storysplat)
|
|
656
|
+
|
|
657
|
+
## License
|
|
658
|
+
|
|
659
|
+
MIT License - see LICENSE file for details
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
body,html{font-family:Arial,sans-serif;height:100%;margin:0;overflow:hidden;padding:0;width:100%}#renderCanvas{display:block;height:100%;touch-action:none;width:100%}@keyframes storysplat-viewer-fadeOut{0%{opacity:1}to{opacity:0}}#preloader{align-items:center;background-color:#1e1e1e;display:flex;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;height:100%;justify-content:center;left:0;position:fixed;top:0;transition:opacity .5s ease-out;width:100%;z-index:100000}#preloader.hidden{opacity:0;pointer-events:none}#preloader-content{align-items:center;display:flex;flex-direction:column;gap:20px;padding:40px}#preloader-media{align-items:center;display:flex;gap:40px}#preloader-image,#preloader-image-inverted{height:200px;object-fit:contain;width:auto}#preloader-image-inverted{filter:invert(1);margin-right:-100px}#preloader-lottie{height:200px;width:200px}@media (max-width:768px){#preloader-image{height:100px}#preloader-image-inverted{height:100px;margin-right:-50px}#preloader-lottie{height:100px;width:100px}}#preloader-progress{background:#ffffff1a;border-radius:2px;height:4px;margin-top:20px;overflow:hidden;width:200px}#preloader-progress-bar{background:var(--storysplat-viewer-accent-color,#4caf50);border-radius:2px;height:100%;position:relative;transition:width .3s ease-out;width:0}#preloader-progress-text{color:#fff;font-size:14px;left:50%;position:absolute;top:-25px;transform:translateX(-50%)}#hotspotContent{background-color:#000c;border-radius:10px;box-shadow:0 0 10px #00000080;color:#fff;display:none;flex-direction:column;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;max-width:300px;padding:20px;position:fixed;z-index:100001}#hotspotContent img{border-radius:5px;height:auto;margin-top:10px;max-width:100%}#hotspotContent.fullscreen{align-items:center!important;border-radius:0!important;bottom:0!important;display:flex!important;flex-direction:column!important;height:100%!important;justify-content:center!important;left:0!important;margin:0!important;max-width:none!important;right:0!important;top:0!important;width:100%!important}#hotspotContent.fullscreen img{height:80vh!important;margin-top:0!important;object-fit:contain!important;width:80%!important}#infoPopup{background-color:#000c;border-radius:10px;box-shadow:0 0 10px #00000080;font-size:16px;left:50%;max-width:80%;padding:20px;position:fixed;top:50%;transform:translate(-50%,-50%);z-index:100002}#infoPopup,.storysplat-hotspot-popup{color:#fff;display:none;font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif}.storysplat-hotspot-popup{background-color:#000000bf;border:1px solid #444;border-radius:6px;box-shadow:0 4px 12px #0006;font-size:14px;line-height:1.5;max-height:450px;max-width:320px;overflow-y:auto;padding:15px;pointer-events:auto;position:absolute;z-index:100}.storysplat-hotspot-popup h3{border-bottom:1px solid #fff3;font-size:1.1em;margin-bottom:10px;margin-top:0;padding-bottom:5px}.storysplat-hotspot-popup p{margin-bottom:10px;margin-top:0;white-space:pre-wrap}.storysplat-hotspot-popup img{border-radius:4px;display:block;height:auto;margin-bottom:10px;margin-top:5px;max-width:100%}.storysplat-hotspot-popup iframe{border:none;height:250px;margin-bottom:10px;margin-top:5px;width:100%}.storysplat-hotspot-popup a{background-color:#007bff;border-radius:4px;color:#fff;display:inline-block;margin-top:10px;padding:8px 15px;text-decoration:none;transition:background-color .2s ease}.storysplat-hotspot-popup a:hover{background-color:#0056b3}.storysplat-hotspot-popup button{background:#323232cc;border:none;border-radius:4px;color:#fff;cursor:pointer;font-size:12px;line-height:1;padding:3px 7px;position:absolute;right:8px;top:8px;transition:background-color .2s ease}.storysplat-hotspot-popup button:hover{background:#505050e6}.storysplat-hotspot-popup::-webkit-scrollbar{width:6px}.storysplat-hotspot-popup::-webkit-scrollbar-track{background:#ffffff1a;border-radius:3px}.storysplat-hotspot-popup::-webkit-scrollbar-thumb{background:#ffffff4d;border-radius:3px}.storysplat-hotspot-popup::-webkit-scrollbar-thumb:hover{background:#ffffff80}.storysplat-progress-bar{background-color:#fff3;border-radius:3px;bottom:20px;height:6px;left:50%;max-width:600px;overflow:hidden;pointer-events:none;position:absolute;transform:translateX(-50%);width:80%;z-index:100}.storysplat-progress-indicator{background-color:var(--storysplat-viewer-accent-color,#fff);border-radius:3px;height:100%;transition:width .1s linear;width:0}.storysplat-scroll-controls{align-items:center;background:#000000b3;border-radius:8px;bottom:0;display:flex;flex-direction:column;gap:5px;left:50%;padding:10px;position:absolute;transform:translateX(-50%);width:150px;z-index:1000}.storysplat-scroll-percentage{color:#fff;font-size:18px}.storysplat-waypoint-info{background:#00000080;color:#fff;display:none;font-family:system-ui,-apple-system,sans-serif;left:0;pointer-events:none;position:absolute;right:0;text-align:center;top:0;z-index:1000}.storysplat-waypoint-info.hasContent{padding:50px 30px 30px}.storysplat-nav-buttons{bottom:40px;display:flex;gap:15px;left:50%;position:absolute;transform:translateX(-50%);z-index:101}.storysplat-nav-button{align-items:center;background-color:#0009;border:1px solid #ffffff4d;border-radius:4px;color:#fff;cursor:pointer;display:flex;font-size:14px;gap:5px;justify-content:center;padding:8px 16px;transition:background-color .2s ease,border-color .2s ease;user-select:none}.storysplat-nav-button:hover:not(:disabled){background-color:#000c;border-color:#fff9}.storysplat-nav-button:disabled{cursor:not-allowed;opacity:.4}.storysplat-camera-mode-toggle,.storysplat-walk-explore-toggle{background-color:#0009;border-radius:8px;display:flex;gap:8px;padding:8px;position:absolute;right:15px;top:15px;z-index:1001}.storysplat-mode-button{background-color:#0009;border:1px solid #ffffff4d;border-radius:4px;color:#fff;cursor:pointer;font-size:13px;padding:6px 12px;transition:background-color .2s ease,border-color .2s ease;user-select:none}.storysplat-mode-button:hover:not(.active){background-color:#fff3;border-color:#ffffff80}.storysplat-mode-button.active{background-color:var(--storysplat-viewer-accent-color,#fff);border-color:var(--storysplat-viewer-accent-color,#fff);color:#1e1e1e;cursor:default}.storysplat-camera-mode-toggle+.storysplat-walk-explore-toggle{top:60px}.storysplat-mobile-joystick{background-color:#0000004d;border-radius:50%;bottom:20px;display:none;height:100px;left:20px;position:absolute;width:100px;z-index:101}.storysplat-mobile-joystick .back{background-color:#0000004d!important;border:1px solid #fff3}.storysplat-mobile-joystick .front{background-color:#ffffff80!important}#storysplat-preloader{align-items:center;background-color:#000000d9;color:#fff;display:flex;flex-direction:column;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;height:100%;justify-content:center;left:0;opacity:1;position:absolute;top:0;transition:opacity .5s ease-out;width:100%;z-index:2000}#storysplat-preloader.hidden{opacity:0;pointer-events:none}#storysplat-preloader-logo{display:block;margin-bottom:20px;max-height:100px;max-width:150px}#storysplat-preloader-logo.empty{display:none}#storysplat-preloader-logo img{display:block;max-height:100%;max-width:100%}#storysplat-preloader-logo>div{align-items:center;background:#333;border-radius:5px;box-sizing:border-box;display:flex;height:100%;justify-content:center;overflow:hidden;padding:5px;text-align:center;text-overflow:ellipsis;white-space:nowrap;width:100%}#storysplat-preloader-progress-container{background-color:#555;border-radius:5px;height:10px;margin-bottom:10px;max-width:300px;overflow:hidden;width:80%}#storysplat-preloader-progress-bar{background-color:var(--storysplat-viewer-preloader-color,var(--storysplat-viewer-accent-color,#4caf50));height:100%;transition:width .3s ease;width:0}#storysplat-progress-text{font-size:14px;margin-top:5px}#startButtonContainer,#storysplat-start-overlay{align-items:center;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);background-color:#fff3;display:flex;height:100%;justify-content:center;left:0;opacity:1;position:absolute;top:0;transition:opacity .5s ease-out;width:100%;z-index:5000}#startButtonContainer.hidden,#storysplat-start-overlay.hidden{opacity:0;pointer-events:none}#startButton,#storysplat-start-button{background-color:#ffffff40;border:none;border-radius:5px;box-shadow:0 4px 8px #0000004d;color:#fff;cursor:pointer;font-size:24px;padding:20px 40px;transition:background-color .3s ease}#startButton:hover,#storysplat-start-button:hover{background-color:#ffffff59}#storysplat-watermark,.watermark{background-color:#00000080;border-radius:4px;bottom:10px;color:#ffffffb3;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:11px;padding:3px 8px;position:fixed;right:10px;text-decoration:none;transition:color .2s ease;z-index:1001}#storysplat-watermark:hover,.watermark:hover{color:#fff}#storysplat-help-button{align-items:center;background-color:#0009;border:1px solid #fff3;border-radius:50%;color:#fff;cursor:pointer;display:flex;font-size:20px;font-weight:700;height:36px;justify-content:center;left:10px;position:absolute;top:10px;transform:scale(1);transition:transform .1s ease-out;width:36px;z-index:1002}#storysplat-help-button:hover{transform:scale(1.1)}#storysplat-help-panel{background-color:#141414e6;border:1px solid #ffffff1a;border-radius:8px;box-shadow:0 5px 15px #0006;color:#eee;display:none;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,sans-serif;font-size:14px;left:10px;line-height:1.6;max-height:calc(100vh - 70px);max-width:350px;opacity:0;overflow-y:auto;padding:15px 20px;position:absolute;top:55px;transform:translateY(-10px);transition:opacity .3s ease,transform .3s ease;z-index:1001}#storysplat-help-panel.visible{display:block;opacity:1;transform:translateY(0)}#storysplat-help-panel h3{border-bottom:1px solid #fff3;color:#fff;margin-bottom:15px;margin-top:0;padding-bottom:8px}#storysplat-help-panel p{margin-bottom:10px}#storysplat-help-panel ul{list-style:disc;margin:0 0 15px 20px;padding:0}#storysplat-help-panel::-webkit-scrollbar{width:6px}#storysplat-help-panel::-webkit-scrollbar-track{background:#ffffff1a;border-radius:3px}#storysplat-help-panel::-webkit-scrollbar-thumb{background:#fff6;border-radius:3px}#storysplat-help-panel::-webkit-scrollbar-thumb:hover{background:#fff9}#muteButton,.storysplat-mute-button{background:#0000004d;border:none;border-radius:3px;bottom:10px;color:#fff;cursor:pointer;font-size:12px;left:10px;padding:5px 8px;position:fixed;transition:background-color .2s ease;z-index:1000}#muteButton:hover,.storysplat-mute-button:hover{background:#00000080}#fullscreenButton{align-items:center;background-color:#00000080;border:none;border-radius:8px;cursor:pointer;display:flex;height:40px;justify-content:center;position:fixed;right:20px;top:20px;transition:background-color .3s ease;width:40px;z-index:1000}#fullscreenButton:hover{background-color:#000c}#fullscreenButton svg{fill:#fff;height:24px;width:24px}#waypointInfo{border-radius:4px;display:none;font-size:14px;margin-bottom:10px}#scrollControls{background-color:#000000b3;border-radius:10px;bottom:45px;box-shadow:0 4px 6px #0000001a;color:#fff;left:50%;max-width:350px;padding:15px;position:absolute;transform:translateX(-50%);z-index:1000}#scrollControls,#scrollControlsContent{align-items:center;display:flex;flex-direction:column}#scrollControlsContent{justify-content:center;transition:opacity .3s ease-in-out;width:100%}#scrollControlsContent.hidden{opacity:0;pointer-events:none;position:absolute}#scrollPercentage{font-size:18px;margin-bottom:10px}#progressBarContainer{background-color:#ffffff4d;border-radius:5px;height:10px;margin-bottom:10px;overflow:hidden;width:200px}#progressBar{height:100%;transition:width .3s ease;width:0}#progressBar,.button{background-color:var(--storysplat-viewer-accent-color,#4caf50)}.button{border:none;border-radius:5px;color:#fff;cursor:pointer;display:inline-block;font-size:16px;margin:4px 2px;padding:10px 20px;text-align:center;text-decoration:none;transition:background-color .3s}#scrollButtons{display:flex;justify-content:space-between;margin-bottom:0;width:100%;z-index:1000}#toggleCameraMode{background-color:var(--storysplat-viewer-accent-color,#4caf50);border:none;border-radius:5px;color:#fff;cursor:pointer;display:inline-block;font-size:16px;margin:4px 2px;padding:10px 20px;text-align:center;text-decoration:none;transition:background-color .3s}#toggleCameraMode:hover,.button:hover{filter:brightness(.9)}#walkToggleButton{background-color:#00000080;border:none;border-radius:5px;bottom:60px;color:#fff;cursor:pointer;display:none;font-size:14px;padding:10px 20px;position:fixed;right:10px;transition:all .3s ease;z-index:1000}#walkToggleButton.selected{background-color:var(--storysplat-viewer-accent-color,#4caf50)}#walkToggleButton:hover{filter:brightness(.9)}@media (max-width:768px){#scrollControls{bottom:20px;max-width:90%;padding:10px}#progressBarContainer{width:150px}#toggleCameraMode,.button{font-size:14px;padding:8px 15px}#scrollPercentage{font-size:16px}#storysplat-help-panel{max-height:calc(100vh - 100px);max-width:calc(100vw - 40px)}.storysplat-hotspot-popup{max-height:60vh;max-width:calc(100vw - 40px)}}.hidden{display:none!important}.fade-out{animation:storysplat-viewer-fadeOut .5s ease-out forwards}
|
|
2
|
+
/*# sourceMappingURL=viewer.css.map */
|