storysplat-viewer 2.7.1 → 2.7.2
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 +198 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -668,6 +668,204 @@ import fs from 'fs';
|
|
|
668
668
|
fs.writeFileSync('scene.html', html);
|
|
669
669
|
```
|
|
670
670
|
|
|
671
|
+
## Portals (Scene-to-Scene Navigation)
|
|
672
|
+
|
|
673
|
+
Portals allow users to navigate between multiple 3D scenes by clicking or walking near portal markers.
|
|
674
|
+
|
|
675
|
+
### How Portals Work
|
|
676
|
+
|
|
677
|
+
Portals are configured in the scene JSON with a `targetSceneId`:
|
|
678
|
+
|
|
679
|
+
```json
|
|
680
|
+
{
|
|
681
|
+
"portals": [
|
|
682
|
+
{
|
|
683
|
+
"id": "portal-1",
|
|
684
|
+
"targetSceneId": "abc123xyz",
|
|
685
|
+
"targetSceneName": "Campervan Interior",
|
|
686
|
+
"position": { "x": 2, "y": 0, "z": -3 },
|
|
687
|
+
"type": "sphere",
|
|
688
|
+
"activationMode": "click"
|
|
689
|
+
}
|
|
690
|
+
]
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### Portal Paths for Self-Hosted Scenes
|
|
695
|
+
|
|
696
|
+
**Important:** By default, portals use StorySplat scene IDs and fetch from `discover.storysplat.com`. For fully self-hosted portals, you need to intercept the `portalActivated` event and implement custom navigation.
|
|
697
|
+
|
|
698
|
+
#### Self-Hosted Portal Example
|
|
699
|
+
|
|
700
|
+
```javascript
|
|
701
|
+
import { createViewer } from 'storysplat-viewer';
|
|
702
|
+
|
|
703
|
+
// Your self-hosted scene folder structure:
|
|
704
|
+
// /scenes/
|
|
705
|
+
// ├── scene_campervan/
|
|
706
|
+
// │ └── scene.json
|
|
707
|
+
// ├── scene_cabin/
|
|
708
|
+
// │ └── scene.json
|
|
709
|
+
// └── scene_garden/
|
|
710
|
+
// └── scene.json
|
|
711
|
+
|
|
712
|
+
// Load initial scene
|
|
713
|
+
const initialScene = await fetch('/scenes/scene_campervan/scene.json').then(r => r.json());
|
|
714
|
+
let viewer = await createViewer(container, initialScene);
|
|
715
|
+
|
|
716
|
+
// Handle portal navigation with custom paths
|
|
717
|
+
viewer.on('portalActivated', async (data) => {
|
|
718
|
+
console.log('Portal clicked:', data.targetSceneId);
|
|
719
|
+
|
|
720
|
+
// Map scene IDs to your folder paths
|
|
721
|
+
const scenePaths = {
|
|
722
|
+
'cabin-scene-id': '/scenes/scene_cabin/scene.json',
|
|
723
|
+
'garden-scene-id': '/scenes/scene_garden/scene.json',
|
|
724
|
+
// Or use the scene ID directly as folder name:
|
|
725
|
+
// [data.targetSceneId]: `/scenes/${data.targetSceneId}/scene.json`
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
const scenePath = scenePaths[data.targetSceneId];
|
|
729
|
+
if (!scenePath) {
|
|
730
|
+
console.error('Unknown scene:', data.targetSceneId);
|
|
731
|
+
return;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Fetch from YOUR server
|
|
735
|
+
const newSceneData = await fetch(scenePath).then(r => r.json());
|
|
736
|
+
|
|
737
|
+
// Dispose old viewer and create new one
|
|
738
|
+
viewer.destroy();
|
|
739
|
+
viewer = await createViewer(container, newSceneData);
|
|
740
|
+
|
|
741
|
+
// Re-attach portal handler for the new scene
|
|
742
|
+
viewer.on('portalActivated', arguments.callee);
|
|
743
|
+
});
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
#### Using Relative Paths
|
|
747
|
+
|
|
748
|
+
If you want to use the `targetSceneId` directly as a folder name:
|
|
749
|
+
|
|
750
|
+
```javascript
|
|
751
|
+
viewer.on('portalActivated', async (data) => {
|
|
752
|
+
// targetSceneId becomes the folder name
|
|
753
|
+
const scenePath = `/scenes/${data.targetSceneId}/scene.json`;
|
|
754
|
+
|
|
755
|
+
const newSceneData = await fetch(scenePath).then(r => r.json());
|
|
756
|
+
viewer.destroy();
|
|
757
|
+
viewer = await createViewer(container, newSceneData);
|
|
758
|
+
});
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
Then structure your folders to match:
|
|
762
|
+
```
|
|
763
|
+
/scenes/
|
|
764
|
+
├── campervan/ ← targetSceneId: "campervan"
|
|
765
|
+
│ └── scene.json
|
|
766
|
+
├── cabin/ ← targetSceneId: "cabin"
|
|
767
|
+
│ └── scene.json
|
|
768
|
+
```
|
|
769
|
+
|
|
770
|
+
#### Fully Self-Hosted (No StorySplat at All)
|
|
771
|
+
|
|
772
|
+
If both scenes are created locally and **never uploaded to StorySplat**, you have full control over the `targetSceneId` values. They don't need to be real StorySplat IDs—use any string identifier you want:
|
|
773
|
+
|
|
774
|
+
**Step 1: Edit your scene.json files to add portals with custom IDs**
|
|
775
|
+
|
|
776
|
+
```json
|
|
777
|
+
// scenes/campervan/scene.json
|
|
778
|
+
{
|
|
779
|
+
"name": "Campervan Tour",
|
|
780
|
+
"portals": [
|
|
781
|
+
{
|
|
782
|
+
"id": "portal-1",
|
|
783
|
+
"targetSceneId": "cabin",
|
|
784
|
+
"targetSceneName": "Mountain Cabin",
|
|
785
|
+
"position": { "x": 2, "y": 0, "z": -3 },
|
|
786
|
+
"type": "sphere",
|
|
787
|
+
"activationMode": "click"
|
|
788
|
+
}
|
|
789
|
+
]
|
|
790
|
+
}
|
|
791
|
+
```
|
|
792
|
+
|
|
793
|
+
```json
|
|
794
|
+
// scenes/cabin/scene.json
|
|
795
|
+
{
|
|
796
|
+
"name": "Mountain Cabin",
|
|
797
|
+
"portals": [
|
|
798
|
+
{
|
|
799
|
+
"id": "portal-back",
|
|
800
|
+
"targetSceneId": "campervan",
|
|
801
|
+
"targetSceneName": "Back to Campervan",
|
|
802
|
+
"position": { "x": -1, "y": 0, "z": 2 },
|
|
803
|
+
"type": "sphere",
|
|
804
|
+
"activationMode": "click"
|
|
805
|
+
}
|
|
806
|
+
]
|
|
807
|
+
}
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
**Step 2: Complete self-hosted viewer code**
|
|
811
|
+
|
|
812
|
+
```javascript
|
|
813
|
+
import { createViewer } from 'storysplat-viewer';
|
|
814
|
+
|
|
815
|
+
const container = document.getElementById('viewer');
|
|
816
|
+
|
|
817
|
+
// Map your scene identifiers to file paths
|
|
818
|
+
const SCENES = {
|
|
819
|
+
'campervan': '/scenes/campervan/scene.json',
|
|
820
|
+
'cabin': '/scenes/cabin/scene.json',
|
|
821
|
+
'garden': '/scenes/garden/scene.json'
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
let currentViewer = null;
|
|
825
|
+
|
|
826
|
+
async function loadScene(sceneId) {
|
|
827
|
+
// Cleanup previous viewer
|
|
828
|
+
if (currentViewer) {
|
|
829
|
+
currentViewer.destroy();
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Fetch scene from YOUR server
|
|
833
|
+
const sceneData = await fetch(SCENES[sceneId]).then(r => r.json());
|
|
834
|
+
|
|
835
|
+
// Create viewer
|
|
836
|
+
currentViewer = await createViewer(container, sceneData);
|
|
837
|
+
|
|
838
|
+
// Handle portal clicks - loads target scene from your server
|
|
839
|
+
currentViewer.on('portalActivated', (data) => {
|
|
840
|
+
loadScene(data.targetSceneId);
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// Start with initial scene
|
|
845
|
+
loadScene('campervan');
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
**Key point:** The `targetSceneId` is just a string you define. It doesn't need to exist on StorySplat—you map it to your own file paths.
|
|
849
|
+
|
|
850
|
+
#### Portal Events
|
|
851
|
+
|
|
852
|
+
| Event | Description | Data |
|
|
853
|
+
|-------|-------------|------|
|
|
854
|
+
| `portalActivated` | Portal clicked or proximity triggered | `{ portalId, targetSceneId, targetSceneName }` |
|
|
855
|
+
| `portalNavigationStart` | Navigation beginning | `{ targetSceneId }` |
|
|
856
|
+
| `portalNavigationComplete` | New scene loaded | `{ sceneId }` |
|
|
857
|
+
| `portalNavigationError` | Navigation failed | `{ error, targetSceneId }` |
|
|
858
|
+
|
|
859
|
+
### Q: Where does the viewer look for scene files?
|
|
860
|
+
|
|
861
|
+
**A: It depends on how you load scenes:**
|
|
862
|
+
|
|
863
|
+
- **`createViewerFromSceneId()`** - Fetches from `discover.storysplat.com/api/scene/{id}`
|
|
864
|
+
- **`createViewer(sceneData)`** - Uses the data object you pass in directly (no fetch)
|
|
865
|
+
- **Splat file URLs in scene JSON** - Can be absolute URLs or relative to your page
|
|
866
|
+
|
|
867
|
+
The viewer itself has no "base path" concept - splat file paths in your scene JSON should be absolute URLs or relative to your HTML page, not to the viewer JS file.
|
|
868
|
+
|
|
671
869
|
## Support
|
|
672
870
|
|
|
673
871
|
- GitHub Issues: [github.com/SonnyC56/storysplat-viewer/issues](https://github.com/SonnyC56/storysplat-viewer/issues)
|
package/package.json
CHANGED