Skip to content

Repository files navigation

SPHR logo

Next.js React TypeScript Three.js License: MIT

SPHR

SPHR is a hackable web runtime for spatial tours, digital twins, and browser-native 3D scenes.

This branch is the Next.js + Three.js migration. It removes the old separate backend requirement and ships as a client-rendered viewer that can load a default demo, a remote JSON config, or embedded bootstrap data from a host page.

sphr-3dgs.mp4

Matterport E57 migration

e57_migration_observatory.mp4

Import a Matterport export ZIP or E57 into a complete local tour:

python3 -m venv .venv-matterport
.venv-matterport/bin/pip install -r scripts/matterport/requirements.txt
npm run import:matterport -- --e57 /path/to/export.zip
npm run dev

Open / to browse thumbnails, search, sort and copy permanent scene links. /library redirects to this collection. See collection and hosting. The importer derives image orientation from camera poses, preserves meters, fuses measured depth into a mesh of at most 50,000 triangles, and validates every scan before publishing. See the migration guide for configuration, geometry conventions, quality checks, and output files.

3D Gaussian Splatting

The reworked SPHR is centered on 3D Gaussian Splatting. The runtime can load splat assets or 360 images, etc directly in the browser, place them in a Three.js scene, animate camera movement through the capture, and layer guided tour text, UI controls, skyboxes, annotations, models, and effects over the scene.

SPHR uses @sparkjsdev/spark for splat rendering. The current splat layer supports:

  • .splat, .ply, .spz, .ksplat, .sog, and .rad sources
  • local or remote URLs loaded through fetch
  • per-splat position, rotation, scale, opacity, and LOD settings
  • reveal animation for initial scene entry
  • guided-tour study modes such as point shrinking, projected splat emphasis, and night mode
  • a separate scene graph for GLB/mesh overlays, raycast targets, and transition meshes

Example splat config:

{
  "space": {
    "title": "Research Garden Capture",
    "type": "splat",
    "space_data": {
      "noPanos": true,
      "initialPosition": { "x": 4, "y": 1.5, "z": 4 },
      "initialRotation": { "azimuth": 0, "polar": 90 },
      "splats": [
        {
          "id": "garden",
          "url": "https://your-asset-host.example/captures/garden.splat",
          "fileType": "splat",
          "position": [0, 2.6, 0],
          "rotation": [-2.56518, -2.66973, 0.2615546],
          "scale": 1,
          "lod": false,
          "reveal": true
        }
      ]
    }
  }
}

Large splat files are intentionally not meant to live in git. Host captures in object storage, a CDN, a research artifact mirror, or a local static directory that is excluded from version control.

What It Renders

SPHR currently supports these runtime layers:

  • 3D Gaussian splats through SparkSplatLayer
  • 360 panorama nodes through PanoramaLayer
  • IIIF image planes through IiifImageLayer
  • custom GLB/model/light hierarchies through SceneGraphLayer
  • 3D annotations through AnnotationLayer
  • navigation markers and floor click navigation
  • equirectangular, cube, and color skyboxes
  • first-person and orbit camera modes
  • guided tours with text, audio, annotations, model visibility, and camera moves

The runtime is not a visual authoring tool. It is the viewer/runtime that renders a structured SPHR scene.

Quickstart

Install dependencies:

npm install

Run the Next.js dev server:

npm run dev

Open http://localhost:3000.

Build and serve a production build:

npm run build
npm run start

Run TypeScript validation:

npm run typecheck

npm run start is for production builds. Use npm run dev while developing.

Opening 3DGS Captures

To open your own capture:

  1. Export or convert the capture to .splat, .ply, .spz, .ksplat, .sog, or .rad.
  2. Put the file behind a URL the browser can fetch. For remote URLs, make sure CORS allows the app origin.
  3. Create a bootstrap JSON with a space.type of splat and a space.space_data.splats entry.
  4. Load it with /?config=/path/to/bootstrap.json.

Useful prompts for coding agents:

Find an openly available 3D Gaussian Splatting research capture with a downloadable .splat, .ply, .spz, .ksplat, .sog, or .rad asset. Confirm the license and whether the URL is browser-fetchable, then generate a SPHR bootstrap JSON that opens it.
Use the Garden scene from recent 3DGS research as the target scene. Locate or prepare a compatible splat asset, keep large binaries out of git, and wire SPHR to it through ?config with correct position, rotation, scale, and optional skybox settings.
Open my local 3DGS capture in SPHR. Inspect the asset format, serve it from an ignored local static path or external asset host, create a bootstrap JSON, and tune the camera start position until the scene opens correctly.

Loading Your Own Scene

The homepage lists imported scenes. A /s/<sceneId>/<title-slug> route selects a package by its saved ID and passes its config to components/SphrApp.tsx. Older title slugs redirect to the current title. Existing query-based scene URLs remain supported.

Inside the viewer, loadBootstrapData() from lib/bootstrap.ts resolves data in this order:

  1. the explicit config selected by the scene route
  2. ?config=/path/to/bootstrap.json
  3. window.__SPHR_BOOTSTRAP__
  4. embedded script tags with IDs space_data, tour_data, ordered_spaces_data, and ui_data
  5. fallback local/demo data (?demo=garden opens the bundled demo)

The recommended integration path is a single bootstrap JSON file:

{
  "space": {
    "id": "my-space",
    "title": "My Space",
    "type": "splat",
    "mesh": "/models/dollhouse.glb",
    "space_data": {
      "initialPosition": { "x": 0, "y": 1.6, "z": 4 },
      "initialRotation": { "azimuth": 0, "polar": 0 },
      "splats": [
        {
          "id": "capture",
          "url": "/captures/scene.splat",
          "scale": 1
        }
      ],
      "sceneGraph": []
    }
  },
  "tour": {
    "id": "my-tour",
    "title": "My Tour",
    "tour_data": {
      "defaultShowText": true,
      "spaces": [
        {
          "id": "my-space",
          "title": "My Space",
          "tourpoints": [
            {
              "viewMode": "FPV",
              "targetType": "FREE",
              "text": "Start here.",
              "position": { "x": 0, "y": 1.6, "z": 4 },
              "rotation": { "azimuth": 0, "polar": 0 },
              "zoom": 0
            }
          ]
        }
      ]
    }
  },
  "ui": {
    "titlePart1": "SPHR",
    "titlePart2": "Demo",
    "enterButtonText": "Enter",
    "exploreButtonText": "Explore"
  }
}

The authoritative schema lives in lib/types.ts.

360 Images And Navigation Transitions

SPHR also supports classic 360-image tours. A 360 space is defined as a set of nodes. Each node has a camera position and either an equirectangular image, six cube faces, or a texture template that resolves to cube faces.

Minimal node example:

{
  "space": {
    "title": "360 Tour",
    "type": "spaces",
    "space_data": {
      "initialNode": "scan-000",
      "nodes": [
        {
          "uuid": "scan-000",
          "image": "/panos/scan-000.jpg",
          "position": { "x": 0, "y": 1.6, "z": 0 },
          "floorPosition": { "x": 0, "y": 0, "z": 0 },
          "rotation": { "x": 0, "y": 0, "z": 0 }
        },
        {
          "uuid": "scan-001",
          "faces": [
            "/panos/scan-001/face0.jpg",
            "/panos/scan-001/face1.jpg",
            "/panos/scan-001/face2.jpg",
            "/panos/scan-001/face3.jpg",
            "/panos/scan-001/face4.jpg",
            "/panos/scan-001/face5.jpg"
          ],
          "position": { "x": 2.5, "y": 1.6, "z": 0 },
          "floorPosition": { "x": 2.5, "y": 0, "z": 0 },
          "rotation": { "x": 0, "y": 0, "z": 0 }
        }
      ]
    }
  }
}

The basic 360 transition crossfades between the outgoing panorama and the incoming panorama while the camera animates to the next node.

SPHR also includes a stronger mesh-based transition for moving between 360 images. During navigation, SphrRuntime captures the outgoing panorama into a WebGLCubeRenderTarget with a CubeCamera. That cube texture is temporarily applied to configured scene graph meshes, usually a low-poly dollhouse or scan mesh, while the main camera flies to the next node. The transition mesh fades out as the new panorama becomes active. This makes the movement feel spatial instead of just cutting between two photos.

Transition config example:

{
  "space_data": {
    "navigationTransition": {
      "enabled": true,
      "meshIds": ["dollhouse"],
      "opacity": 0.2,
      "meshFadeMs": 400,
      "navigationMs": 1100,
      "cubeRenderTargetSize": 2048
    },
    "sceneGraph": [
      {
        "id": "dollhouse",
        "type": "model",
        "file": "/models/dollhouse.glb",
        "visible": true,
        "persistent": true,
        "raycast": true,
        "fpvOpacity": 0,
        "orbitOpacity": 1,
        "transitionMesh": true,
        "transitionOpacity": 0.2,
        "transitionFadeMs": 400,
        "transitionTexture": "cube-render-target"
      }
    ]
  }
}

For this transition to run, the space needs:

  • panorama nodes with images or cube faces
  • a scene graph mesh marked as a transition mesh
  • navigationTransition.enabled not set to false
  • first-person navigation between two different nodes

Set noPanos: true for splat-only spaces where panorama rendering should be disabled.

Tours

Tours are arrays of spaces and tourpoints. A tourpoint can move the camera, switch view mode, show text, reveal models, show annotations, play sounds, or trigger runtime modes through the extra field.

Common tourpoint fields:

  • viewMode: FPV or ORBIT
  • targetType: NODE, FREE, or MODEL
  • nodeUUID: node to navigate to in a 360 tour
  • position: free camera position for splat or free-flight points
  • rotation: camera azimuth and polar angles
  • zoom: camera FOV adjustment
  • text and secondaryText: guided tour copy
  • models: scene graph IDs to show at this step
  • annotations or overlays: annotation IDs to show at this step
  • sounds: audio IDs to play at this step
  • extra: runtime mode such as nightMode, shrinkToPoints, or projectToSplats

Scene Graph

The scene graph lets a tour add persistent or step-specific 3D content around the main capture.

Supported node uses include:

  • GLB or other model files
  • groups
  • ambient, point, directional, and custom lights
  • raycast meshes for cursor and floor navigation
  • FPV, orbit, and debug-specific opacity
  • transition meshes for the 360 navigation effect
  • nested children with inherited transforms

Transforms use arrays for scene graph nodes:

{
  "id": "artifact",
  "type": "model",
  "file": "/models/artifact.glb",
  "position": [0, 1.2, -2],
  "rotation": [0, 0.4, 0],
  "scale": 1,
  "visible": false,
  "raycast": true
}

License

MIT, but if this is useful to you, please cite this repo because it helps the work continue.

@misc{hollis2024sphr,
  author = {Luke Hollis},
  title = {SPHR - Spatial Human Reality in a web browser},
  year = {2024},
  publisher = {GitHub},
  journal = {GitHub repository},
  howpublished = {\url{https://github.com/lukehollis/sphr}}
}

About

Open, hackable 3d digital twin builder that supports 3d gaussian splatting, 360 images, and vfx

Topics

Resources

Stars

57 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages