@cavegiant/cave-world-r3f
v1.1.0
Published
Web-based 3D virtual tour SDK — render interactive 3D scenes from JSON configuration
Downloads
91
Maintainers
Readme
@cavegiant/cave-world-r3f
Web-based 3D virtual tour SDK. Renders an interactive, walkable 3D scene from a single JSON description — including Gaussian-splat captures, collision, interactive objects, and seamless travel between connected scenes.
ESM-only. Consumers must use import (not require).
Install
npm install @cavegiant/cave-world-r3fPeer dependencies (all required):
npm install react@">=18 <19.3" react-dom@">=18 <19.3" three @react-three/fiber @react-three/drei @react-three/viverse @sparkjsdev/spark@^0.1.10 zustand
@react-three/fibercurrently requires React>=19 <19.3(or React 18). Do not install React 19.3+ until fiber widens its peer range.
@sparkjsdev/sparkis pinned to0.1.xon purpose. Spark 2.x rebuilds its splat accumulator whenever the view changes, which injects frame-time jitter that the character integrator turns into visible stutter while walking. Seedoc/ARCHITECTURE.md.If you hit
@react-three/timelineissues with viverse, pin@react-three/[email protected]in the host app as well — this package'soverridesonly apply to its own install.
Quick start
The container needs a definite width and height — the viewer fills it. Omitting
caveSpaceLoader uses DefaultCaveSpaceLoader (the public CaveGiant content API).
import { CaveExplore } from '@cavegiant/cave-world-r3f/explore'
export default function App() {
return (
<div style={{ width: '100vw', height: '100vh' }}>
<CaveExplore spaceId="your-space-id" />
</div>
)
}Loading scenes from your own backend
import { CaveExplore, createHttpCaveSpaceLoader } from '@cavegiant/cave-world-r3f/explore'
const loader = createHttpCaveSpaceLoader({
buildUrl: (spaceId) => `/api/spaces/${spaceId}`,
headers: { Authorization: `Bearer ${token}` }
})
<CaveExplore spaceId={id} caveSpaceLoader={loader} />createHttpCaveSpaceLoader adds a request timeout, non-2xx detection, and payload validation on top of fetch, and reports failures as CaveWorldError with a stable code. Any (spaceId) => Promise<CaveSpace> function works too.
Features
- JSON-driven — scene content is fully described by one config file
- Gaussian splats — native 3DGS rendering with device-aware LOD selection
- Cross-scene travel — seamless walk-through and portal teleport between scenes, with neighbor preloading
- Extensible objects — register custom 3D object types through
ObjectRegistry(unregistered types render nothing; enablesetCaveWorldLogLevel('debug')to see skips) - Extensible behaviors — attach reusable components to any object through
ComponentRegistry - Typed event bus —
object:click,connector:enter,detail:open,custom:*, and more - Custom avatars — third-person GLB/VRM with VRM 1.0 bone mapping
- Multiplayer — optional WebSocket character sync (requires explicit
url+ your avatar catalog) - Replaceable UI — every built-in overlay has a
render*prop, and all copy is translatable - Load-failure hardening — automatic splat retry, stall watchdog, and a retry prompt instead of an infinite spinner
Wrap the viewer (or your own R3F canvas) in a React error boundary if you need host-level recovery from unexpected WebGL/R3F throws — the SDK's load-error overlay covers the hardened splat/config path only.
Entry points
| Import path | Contents |
| -------------------------------------- | ------------------------------------------------------------------------------------- |
| @cavegiant/cave-world-r3f | Common re-exports from all layers (CaveSpace is re-exported as CaveSpaceRenderer) |
| @cavegiant/cave-world-r3f/types | Scene JSON types only — zero runtime |
| @cavegiant/cave-world-r3f/core | Event bus, registries, provider, hooks, logger, i18n |
| @cavegiant/cave-world-r3f/space | Scene rendering components (R3F), including CaveSpace |
| @cavegiant/cave-world-r3f/explore | Viewers, overlays, character, multiplayer |
| @cavegiant/cave-world-r3f/styles.css | Default overlay stylesheet |
Viewers
| Component | Use it for |
| ----------------- | -------------------------------------------------------------------- |
| CaveExplore | First/third-person walkable tour with physics and cross-scene travel |
| CaveOrbitViewer | Orbit-camera overview of a single scene (no avatar, no physics) |
Styling
The built-in overlays ship a small stylesheet, namespaced under .cw-* and themed entirely through --cw-* custom properties. It is injected at runtime by default, so nothing is required to get a working UI.
The defaults are declared on :root, which means setting the same property on any ancestor of the viewer overrides them.
/* Re-theme without replacing any component */
:root {
--cw-color-accent: #7c9cff;
--cw-color-surface: rgb(20 20 28 / 88%);
--cw-radius-md: 4px;
}The properties you are most likely to want:
| Property | Themes |
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
| --cw-color-accent / --cw-color-accent-soft | Primary gold: eyebrow text, key caps, hairlines, hover rules |
| --cw-color-accent-mint | Secondary accent: hover states and the action-panel status dot |
| --cw-color-accent-warm | Load-error badge and its primary button |
| --cw-color-text / --cw-color-text-muted | Overlay copy |
| --cw-color-surface / --cw-color-surface-solid / --cw-color-scrim | Panel, dialog, and detail-window backgrounds |
| --cw-color-label-surface / --cw-color-label-border | In-scene connector tags |
| --cw-panel-fill / --cw-panel-rim / --cw-panel-clip / --cw-panel-grain | Action-panel HUD decoration (set --cw-panel-clip: none for square corners) |
| --cw-radius-*, --cw-blur*, --cw-shadow-*, --cw-z-* | Shape, depth, stacking |
To bundle the CSS yourself instead, import it and turn the injector off:
import '@cavegiant/cave-world-r3f/styles.css'
;<CaveExplore spaceId={id} injectStyles={false} />Localization
All built-in copy defaults to Chinese and is overridable per string. messages is deep-merged over the default locale, so partial overrides keep everything else intact.
import { messagesEn } from '@cavegiant/cave-world-r3f/core'
<CaveExplore spaceId={id} messages={messagesEn} />
<CaveExplore spaceId={id} messages={{ loadError: { retry: 'Try again' } }} />Diagnostics
The SDK only logs warnings and errors by default, and never writes to the console outside that.
import { setCaveWorldLogLevel, setCaveWorldLogSink } from '@cavegiant/cave-world-r3f/core'
setCaveWorldLogLevel('debug') // while integrating (also logs unknown object types)
setCaveWorldLogLevel('silent') // mute entirely
setCaveWorldLogSink(myTelemetryAdapter) // forward to your own pipelineDocumentation
| Document | Audience |
| ------------------------------------------------------------ | ----------------------------------------------------------------- |
| doc/API.md | Integration guide, props reference, event list, extension recipes |
| doc/ARCHITECTURE.md | Module design, data flow, dependency rules |
| doc/scene-json-structure.md | Scene JSON schema |
| doc/multiplayer-protocol.md | WebSocket message protocol |
| AGENTS.md | Invariants and recipes for AI coding agents (repo only) |
| examples/ | Runnable demos — npm run examples |
| CONTRIBUTING.md | Setup, checks, PR expectations |
| SECURITY.md | Vulnerability reporting |
Development
This repository is the standalone package. From the package root:
npm install
npm run check # format:check + typecheck + lint + test
npm run build
npm run verify:package # publint + attw
npm run verify:pack-size # tarball must stay lean (no examples/)
npm run examples # live playground at https://localhost:5173/Nested under a host monorepo
If this tree lives inside a larger portal repo, do not run npm install here (nested node_modules duplicates React / Three.js). Use the host's sdk:* scripts instead (npm run sdk:examples, sdk:check, …). See AGENTS.md.
The playground mounts every examples/0N-*.tsx against the public demo scene
id in examples/demo-space.ts. See
examples/README.md.
License
MIT
